From 0edf36e5b579a697e0b18f36c450b9f60286b77c Mon Sep 17 00:00:00 2001 From: Damien <damien.burel@maarch.org> Date: Thu, 16 Jan 2020 18:11:03 +0100 Subject: [PATCH] FEAT #11788 TIME 2:50 Substituted signatures are back --- lang/en.json | 5 ++- lang/fr.json | 4 +- rest/index.php | 1 + sql/20XX.sql | 3 ++ sql/structure.sql | 1 + .../controllers/DocumentController.php | 15 ++++--- .../user/controllers/SignatureController.php | 45 +++++++++++++++++-- .../app/document/document.component.html | 14 +----- .../app/document/document.component.ts | 14 +++++- .../app/profile/profile.component.html | 8 +++- src/frontend/app/profile/profile.component.ts | 8 ++++ .../app/service/signatures.service.ts | 2 + .../app/signatures/signatures.component.html | 12 ++++- .../app/signatures/signatures.component.ts | 3 +- 14 files changed, 107 insertions(+), 28 deletions(-) diff --git a/lang/en.json b/lang/en.json index 821c9616c8..abf3e395b0 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1,6 +1,7 @@ { "lang": { "actionDone" : "Action done", + "actionDoneInPlaceOf" : "Action done in place of", "administrations" : "Administrations", "afterClickingSendLinkChangePassword": "After clicking on <b>Send</b>, you will receive an email containing a link to change your password. This link will be active <b>1 hour</b>.", "annotateDocument" : "Annotate this document", @@ -158,6 +159,7 @@ "search": "Search", "substitute": "Substitute user", "substitution": "Substitution", + "signSubstituted": "Signatures to substitute", "chooseSubstitute": "Choose a substitute", "noResult": "No result", "circuit": "Circuit", @@ -167,8 +169,9 @@ "attachment": "attachment", "deleteSubstitution": "Delete substitution", "substitutionDeleted": "Substitution deleted", - "substitutionInfo": "You are in substitution mode, you can't make any action", + "substitutionInfo": "You are in substitution mode", "substitutedDoc": "Substituted document", + "substitutedSignature": "Substituted signature", "substituteMsg": "You act as", "substitutionWarn": "You choose a substitution, you will not be able to make any action.", "manage_users": "Users", diff --git a/lang/fr.json b/lang/fr.json index 6e8b5d6c65..722b82e00e 100755 --- a/lang/fr.json +++ b/lang/fr.json @@ -1,6 +1,7 @@ { "lang": { "actionDone" : "Action effectuée", + "actionDoneInPlaceOf" : "Action effectuée à la place de", "administrations" : "Administrations", "afterClickingSendLinkChangePassword": "Après avoir cliqué sur <b>Envoyer</b>, vous recevrez un courriel contenant un lien pour modifier votre mot de passe. Ce lien sera actif <b>1 heure</b>.", "annotateDocument" : "Annoter ce document", @@ -167,8 +168,9 @@ "attachment": "annexe", "deleteSubstitution": "Supprimer la délégation", "substitutionDeleted": "Délégation supprimée", - "substitutionInfo": "Vous avez délégué vos documents, vous ne pouvez pas faire d'action", + "substitutionInfo": "Vous avez délégué vos documents", "substitutedDoc": "Document délégué", + "substitutedSignature": "Signature déléguée", "substituteMsg": "Vous agissez en tant que", "substitutionWarn": "Vous avez choisi une délégation, vous ne pourrez plus faire d'action.", "manage_users": "Utilisateurs", diff --git a/rest/index.php b/rest/index.php index 32d1c85c3c..cd26cf3a80 100755 --- a/rest/index.php +++ b/rest/index.php @@ -113,6 +113,7 @@ $app->get('/users/{id}/signatures', \User\controllers\SignatureController::class $app->post('/users/{id}/signatures', \User\controllers\SignatureController::class . ':create'); $app->delete('/users/{id}/signatures/{signatureId}', \User\controllers\SignatureController::class . ':delete'); $app->put('/users/{id}/externalSignatures', \User\controllers\SignatureController::class . ':updateExternalSignatures'); +$app->patch('/users/{id}/signatures/{signatureId}/substituted', \User\controllers\SignatureController::class . ':updateSubstituted'); //Emails $app->post('/emails', \Email\controllers\EmailController::class . ':send'); diff --git a/sql/20XX.sql b/sql/20XX.sql index da835ac059..963df04f35 100755 --- a/sql/20XX.sql +++ b/sql/20XX.sql @@ -11,3 +11,6 @@ ALTER TABLE main_documents ADD COLUMN notes jsonb; ALTER TABLE main_documents DROP COLUMN IF EXISTS link_id; ALTER TABLE main_documents ADD COLUMN link_id text; + +ALTER TABLE signatures DROP COLUMN IF EXISTS substituted; +ALTER TABLE signatures ADD COLUMN substituted boolean DEFAULT FALSE NOT NULL; diff --git a/sql/structure.sql b/sql/structure.sql index a8dc4a9c5c..0bffda92e6 100755 --- a/sql/structure.sql +++ b/sql/structure.sql @@ -183,6 +183,7 @@ CREATE TABLE signatures path character varying(255) NOT NULL, filename character varying(255) NOT NULL, fingerprint character varying(255) NOT NULL, + substituted boolean DEFAULT FALSE NOT NULL, external_application CHARACTER VARYING(255), CONSTRAINT signatures_pkey PRIMARY KEY (id) ) diff --git a/src/app/document/controllers/DocumentController.php b/src/app/document/controllers/DocumentController.php index bdcd9b0d80..c647d7c1f3 100755 --- a/src/app/document/controllers/DocumentController.php +++ b/src/app/document/controllers/DocumentController.php @@ -398,16 +398,11 @@ class DocumentController return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']); } - $currentUser = UserModel::getById(['id' => $GLOBALS['id'], 'select' => ['substitute']]); - if (!empty($currentUser['substitute'])) { - return $response->withStatus(403)->withJson(['errors' => 'User can not make action with substituted account']); - } - if (empty(DocumentController::ACTIONS[$args['actionId']])) { return $response->withStatus(400)->withJson(['errors' => 'Action does not exist']); } - $workflow = WorkflowModel::getCurrentStep(['select' => ['id', 'mode'], 'documentId' => $args['id']]); + $workflow = WorkflowModel::getCurrentStep(['select' => ['id', 'mode', 'user_id'], 'documentId' => $args['id']]); $body = $request->getParsedBody(); if (!empty($body['signatures'])) { @@ -580,12 +575,18 @@ class DocumentController EmailController::sendNotificationToNextUserInWorkflow(['documentId' => $args['id'], 'userId' => $GLOBALS['id']]); + $historyMessagePart = "{actionDone} : "; + if ($workflow['user_id'] != $GLOBALS['id']) { + $user = UserModel::getLabelledUserById(['id' => $workflow['user_id']]); + $historyMessagePart = "{actionDoneInPlaceOf} {$user} : "; + } + HistoryController::add([ 'code' => 'OK', 'objectType' => 'main_documents', 'objectId' => $args['id'], 'type' => 'ACTION', - 'message' => "{actionDone} : " . DocumentController::ACTIONS[$args['actionId']], + 'message' => $historyMessagePart . DocumentController::ACTIONS[$args['actionId']], 'data' => ['actionId' => $args['actionId']] ]); diff --git a/src/app/user/controllers/SignatureController.php b/src/app/user/controllers/SignatureController.php index 0fdc9198d5..87f1c8d1aa 100755 --- a/src/app/user/controllers/SignatureController.php +++ b/src/app/user/controllers/SignatureController.php @@ -28,18 +28,30 @@ class SignatureController { public function get(Request $request, Response $response, array $args) { + if ($GLOBALS['id'] != $args['id']) { + $user = UserModel::getById(['id' => $args['id'], 'select' => ['substitute']]); + if (empty($user) || $user['substitute'] != $GLOBALS['id']) { + return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']); + } + } + $docserver = DocserverModel::getByType(['type' => 'SIGNATURE', 'select' => ['path']]); if (empty($docserver['path']) || !is_dir($docserver['path'])) { return $response->withStatus(400)->withJson(['errors' => 'Docserver \'SIGNATURE\' does not exist']); } + $where = ['user_id = ?']; + if ($GLOBALS['id'] != $args['id']) { + $where[] = 'substituted = true'; + } $rawSignatures = SignatureModel::get([ - 'select' => ['id', 'path', 'filename', 'fingerprint'], - 'where' => ['user_id = ?'], - 'data' => [$GLOBALS['id']], + 'select' => ['id', 'path', 'filename', 'fingerprint', 'substituted'], + 'where' => $where, + 'data' => [$args['id']], 'orderBy' => ['id DESC'] ]); + $signatures = []; foreach ($rawSignatures as $signature) { $pathToSignature = $docserver['path'] . $signature['path'] . $signature['filename']; @@ -48,6 +60,7 @@ class SignatureController if ($signature['fingerprint'] == $fingerprint) { $signatures[] = [ 'id' => $signature['id'], + 'substituted' => $signature['substituted'], 'encodedSignature' => base64_encode(file_get_contents($pathToSignature)) ]; } @@ -191,4 +204,30 @@ class SignatureController return $response->withStatus(204); } + + public function updateSubstituted(Request $request, Response $response, array $args) + { + if ($GLOBALS['id'] != $args['id'] && !PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) { + return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']); + } + + $user = UserModel::getById(['select' => [1], 'id' => $args['id']]); + if (empty($user)) { + return $response->withStatus(400)->withJson(['errors' => 'User does not exist']); + } + + $body = $request->getParsedBody(); + + if (!Validator::boolType()->validate($body['substituted'])) { + return $response->withStatus(400)->withJson(['errors' => "Body substituted is not set or not a boolean"]); + } + + SignatureModel::update([ + 'set' => ['substituted' => $body['substituted'] ? 'true' : 'false'], + 'where' => ['user_id = ?', 'id = ?'], + 'data' => [$args['id'], $args['signatureId']] + ]); + + return $response->withStatus(204); + } } diff --git a/src/frontend/app/document/document.component.html b/src/frontend/app/document/document.component.html index 92accb587e..1dd55e6c59 100755 --- a/src/frontend/app/document/document.component.html +++ b/src/frontend/app/document/document.component.html @@ -76,23 +76,13 @@ <div class="page-info-page">{{'lang.page' | translate}} {{ pageNum }} / {{ totalPages }}</div> </section> <footer class="footer" - *ngIf="!this.signaturesService.annotationMode && !freezeSidenavClose && currentDoc === 0 && authService.user.substitute === null" + *ngIf="!this.signaturesService.annotationMode && !freezeSidenavClose && currentDoc === 0" [@slideUp]> <ng-container *ngFor="let action of actionsList;"> <button [style.color]="action.color" [style.borderColor]="action.color" class="btn" (click)="launchEvent(action)"><i class="{{action.logo}} fa-2x"></i>{{action.label | translate}}</button> </ng-container> </footer> - <footer class="footer substutionModal" *ngIf="!freezeSidenavClose && currentDoc === 0 && authService.user.substitute !== null" [@slideUp]> - <div class="msgModal"> - {{'lang.substitutionInfo' | translate}} - </div> - <div> - <button mat-icon-button title="{{'lang.deleteSubstitution' | translate}}" (click)="deleteSubstution()"> - <mat-icon fontSet="fas" fontIcon="fa-times"></mat-icon> - </button> - </div> - </footer> <app-drawer></app-drawer> </mat-sidenav-content> <mat-sidenav #snavRight disableClose [mode]="this.signaturesService.mobileMode ? 'over': 'side'" @@ -109,4 +99,4 @@ <app-main-document-detail #appMainDocumentDetail [snavRightComponent]="this.snavRight" [mainDocument]="mainDocument" *ngIf="signaturesService.sideNavRigtDatas.mode == 'mainDocumentDetail'"></app-main-document-detail> </mat-sidenav> -</mat-sidenav-container> \ No newline at end of file +</mat-sidenav-container> diff --git a/src/frontend/app/document/document.component.ts b/src/frontend/app/document/document.component.ts index 8adc5539a4..0a48cee7bf 100755 --- a/src/frontend/app/document/document.component.ts +++ b/src/frontend/app/document/document.component.ts @@ -173,6 +173,18 @@ export class DocumentComponent implements OnInit { this.initDoc(); + const realUserWorkflow = this.mainDocument.workflow.filter((line: { current: boolean; }) => line.current === true)[0]; + + if (realUserWorkflow.userId !== this.authService.user.id) { + this.http.get('../rest/users/' + realUserWorkflow.userId + '/signatures') + .subscribe((dataSign: any) => { + this.signaturesService.signaturesListSubstituted = dataSign.signatures; + this.signaturesService.loadingSign = false; + }); + } else { + this.signaturesService.signaturesListSubstituted = []; + } + this.docList.push({ 'id': this.mainDocument.id, 'title': this.mainDocument.title, 'pages': this.mainDocument.pages, 'imgContent': [], 'imgUrl': '../rest/documents/' + this.mainDocument.id + '/thumbnails' }); this.mainDocument.attachments.forEach((attach: any) => { this.docList.push({ 'id': attach.id, 'title': attach.title, 'pages': attach.pages, 'imgContent': [], 'imgUrl': '../rest/attachments/' + attach.id + '/thumbnails' }); @@ -328,7 +340,7 @@ export class DocumentComponent implements OnInit { e = e.srcEvent; - if (!this.signaturesService.annotationMode && this.currentDoc === 0 && this.authService.user.substitute === null) { + if (!this.signaturesService.annotationMode && this.currentDoc === 0) { this.backToDetails(); diff --git a/src/frontend/app/profile/profile.component.html b/src/frontend/app/profile/profile.component.html index e7979e10b4..2dfb918d7e 100644 --- a/src/frontend/app/profile/profile.component.html +++ b/src/frontend/app/profile/profile.component.html @@ -218,6 +218,12 @@ </plugin-autocomplete> </div> </div> + <fieldset *ngIf="authService.user.substitute != null && signaturesService.signaturesList.length > 0"> + <legend align="left">{{'lang.signSubstituted' | translate}} :</legend> + <ng-container> + <button type="button" class="signListButton" mat-stroked-button *ngFor="let signature of signaturesService.signaturesList; let i=index" (click)="toggleSignature(i)" [class.selected]="signature.substituted"><img [src]="sanitizer.bypassSecurityTrustUrl('data:image/png;base64,' + signature.encodedSignature)"/></button> + </ng-container> + </fieldset> </fieldset> </div> </div> @@ -234,4 +240,4 @@ </span> </form> </div> -</div> \ No newline at end of file +</div> diff --git a/src/frontend/app/profile/profile.component.ts b/src/frontend/app/profile/profile.component.ts index 31af9acaab..29a2fe3b8e 100644 --- a/src/frontend/app/profile/profile.component.ts +++ b/src/frontend/app/profile/profile.component.ts @@ -381,4 +381,12 @@ export class ProfileComponent implements OnInit { setLang(lang: any) { this.translate.use(lang); } + + toggleSignature(i: number) { + this.http.patch('../rest/users/' + this.authService.user.id + '/signatures/' + this.signaturesService.signaturesList[i].id + '/substituted', { 'substituted': !this.signaturesService.signaturesList[i].substituted }) + .subscribe(() => { + this.signaturesService.signaturesList[i].substituted = !this.signaturesService.signaturesList[i].substituted; + this.notificationService.success('lang.modificationSaved'); + }); + } } diff --git a/src/frontend/app/service/signatures.service.ts b/src/frontend/app/service/signatures.service.ts index 51110434ae..71d30a0198 100755 --- a/src/frontend/app/service/signatures.service.ts +++ b/src/frontend/app/service/signatures.service.ts @@ -7,6 +7,7 @@ export class SignaturesContentService { signaturesContent: any[] = []; notesContent: any[] = []; signaturesList: any[] = []; + signaturesListSubstituted: any[] = []; currentPage = 1; totalPage = 1; isTaggable = true; @@ -62,6 +63,7 @@ export class SignaturesContentService { this.signaturesContent = []; this.notesContent = []; this.signaturesList = []; + this.signaturesListSubstituted = []; this.currentPage = 1; this.totalPage = 1; this.isTaggable = true; diff --git a/src/frontend/app/signatures/signatures.component.html b/src/frontend/app/signatures/signatures.component.html index 089425ad66..10debd86e5 100755 --- a/src/frontend/app/signatures/signatures.component.html +++ b/src/frontend/app/signatures/signatures.component.html @@ -9,6 +9,16 @@ <i class="fas fa-pen-nib fa-2x"></i> {{'lang.createNewSignature' | translate}} </div> + <div [@listAnimation]="signaturesService.signaturesListSubstituted.length" style="display: contents;"> + <div *ngFor="let signature of signaturesService.signaturesListSubstituted;let i=index" class="list-item" [title]="'lang.selectSignature' | translate" style="position: relative;overflow: hidden" (tap)="tapEvent(signature,i,'substitute')"> + <button mat-mini-fab color="primary" [title]="'lang.selectSignature' | translate" class="sign_icon add_icon" (tap)="selectSignature(signature,'imgSignSub_'+i);"> + <mat-icon class="fa fa-arrow-up"></mat-icon> + </button> + <img id="imgSignSub_{{i}}" [src]="sanitization.bypassSecurityTrustUrl('data:image/png;base64,' + signature.encodedSignature)" + style="width: 190px;"> + <div class="substituteInfo">{{'lang.substitutedSignature' | translate}}</div> + </div> + </div> <div [@listAnimation]="signaturesService.signaturesList.length" style="display: contents;"> <div *ngFor="let signature of signaturesService.signaturesList;let i=index" class="list-item" [title]="'lang.selectSignature' | translate" style="position: relative;overflow: hidden" (tap)="tapEvent(signature,i,'')"> <button mat-mini-fab color="warn" [title]="'lang.removeSignature' | translate" class="sign_icon remove_icon" (tap)="removeSignature(signature,i);"> @@ -23,4 +33,4 @@ </div> </section> </article> -<app-pad (reloaded)="reloadSignatures()"></app-pad> \ No newline at end of file +<app-pad (reloaded)="reloadSignatures()"></app-pad> diff --git a/src/frontend/app/signatures/signatures.component.ts b/src/frontend/app/signatures/signatures.component.ts index b70f12a0a4..832bd8a6fd 100755 --- a/src/frontend/app/signatures/signatures.component.ts +++ b/src/frontend/app/signatures/signatures.component.ts @@ -119,7 +119,8 @@ export class SignaturesComponent implements OnInit { this.count = 0; } else if (this.count > 1) { this.count = 0; - this.selectSignature(signature, 'imgSign_' + i); + const id = mode === 'substitute' ? ('imgSignSub_' + i) : ('imgSign_' + i); + this.selectSignature(signature, id); } }, 250); } -- GitLab