From d4043587bfd817958353aabe0ff52ea5e5d60bbe Mon Sep 17 00:00:00 2001
From: Alex ORLUC <alex.orluc@maarch.org>
Date: Mon, 9 Jul 2018 20:26:44 +0200
Subject: [PATCH] FEAT #7659 add change password component

---
 .../password-modification.component.html      |  39 +++++-
 .../app/password-modification.component.ts    | 123 ++++++++++++++++--
 .../js/angular/lang/lang-fr.ts                |   9 +-
 3 files changed, 151 insertions(+), 20 deletions(-)

diff --git a/apps/maarch_entreprise/Views/password-modification.component.html b/apps/maarch_entreprise/Views/password-modification.component.html
index 8b607ef09a8..fa1c4d75798 100644
--- a/apps/maarch_entreprise/Views/password-modification.component.html
+++ b/apps/maarch_entreprise/Views/password-modification.component.html
@@ -3,22 +3,47 @@
         <button mat-button (click)="snav.toggle()">
             <mat-icon class="maarchLogo" [svgIcon]="mobileQuery.matches ? 'maarchLogoOnly' : 'maarchLogo'"></mat-icon>
         </button>
-        <h1 class="admin-toolbar-title">{{lang.myProfile}}
-            <small [class.hide-for-mobile]="mobileQuery.matches">{{user.firstname}} {{user.lastname}}</small>
+        <h1 class="admin-toolbar-title">Changement de mot de passe
         </h1>
         <span style="flex: 1 1 auto;"></span>
-        <menu-top></menu-top>
-        <button mat-icon-button (click)="snav2.toggle()">
-            <mat-icon class="fa fa-cog fa-2x"></mat-icon>
-        </button>
+        <button mat-button><mat-icon mat-list-icon class="fa fa-user fa-2x" style="color:white; margin:0px 12px 8px 0px;"></mat-icon>{{user.firstname.charAt(0) | uppercase}}. {{user.lastname | uppercase}}</button>
     </mat-toolbar>
     <mat-sidenav-container class="admin-sidenav-container" [style.marginTop.px]="mobileQuery.matches ? 56 : 0">
+        <mat-sidenav #snav [mode]="mobileQuery.matches ? 'over' : 'side'" [fixedInViewport]="mobileQuery.matches" fixedTopGap="56"
+            [opened]="mobileQuery.matches ? false : true">
+        </mat-sidenav>
         <mat-sidenav-content>
             <div *ngIf="loading" style="display:flex;height:100%;">
                 <mat-spinner style="margin:auto;"></mat-spinner>
             </div>
-            <mat-card *ngIf="!loading" class="card-app-content">
+            <mat-card *ngIf="!loading" class="card-app-content" style="display: flex;justify-content: center;align-items: center;">
+                <form [formGroup]="firstFormGroup" style="max-width: 700px;">
+                    <ng-template>Changement de mot de passe</ng-template>
+                    <mat-form-field>
+                        <input matInput #inputPasswd placeholder="Veuillez définir un nouveau mot de passe" formControlName="firstCtrl" required
+                            [type]="hidePassword ? 'password' : 'text'">
+                        <mat-icon matSuffix (click)="hidePassword = !hidePassword" class="fa fa-2x" [ngClass]="[hidePassword ? 'fa-eye-slash' : 'fa-eye']"></mat-icon>
+                        <mat-hint *ngIf="validPassword">
+                            <i color="accent" class="fa fa-check"></i>
+                            <span color="accent">Mot de passe conforme</span>
+                        </mat-hint>
+                        <mat-hint *ngIf="!validPassword">{{this.ruleText}}</mat-hint>
+                        <mat-error>{{getErrorMessage()}}</mat-error>
+                    </mat-form-field>
+                    <mat-form-field>
+                        <input matInput #inputPasswd placeholder="Re-taper votre nouveau mot de passe" required [type]="hidePassword ? 'password' : 'text'">
+                        <mat-icon matSuffix (click)="hidePassword = !hidePassword" class="fa fa-2x" [ngClass]="[hidePassword ? 'fa-eye-slash' : 'fa-eye']"></mat-icon>
+                    </mat-form-field>
+                    <div class="col-md-12 text-center" style="padding:10px;">
+                            <button mat-raised-button color="primary">{{lang.validate}}</button>
+                            <button mat-raised-button color="default">{{lang.logout}}</button>
+                        </div>
+                </form>
+                <div *ngIf="passwordRules.renewal.enabled" class="alert alert-warning" role="alert" [innerHTML]="OtherRuleText" style="text-align:center;left:0px;bottom: 0px;position: absolute;width: 100%;margin: 0;"></div>
             </mat-card>
         </mat-sidenav-content>
+        <mat-sidenav #snav2 [mode]="mobileQuery.matches ? 'over' : 'side'" [fixedInViewport]="mobileQuery.matches" fixedTopGap="56"
+            position='end' [opened]="mobileQuery.matches ? false : false">
+        </mat-sidenav>
     </mat-sidenav-container>
 </div>
\ No newline at end of file
diff --git a/apps/maarch_entreprise/js/angular/app/password-modification.component.ts b/apps/maarch_entreprise/js/angular/app/password-modification.component.ts
index 34e2091a72d..8b77c4c68b6 100644
--- a/apps/maarch_entreprise/js/angular/app/password-modification.component.ts
+++ b/apps/maarch_entreprise/js/angular/app/password-modification.component.ts
@@ -3,7 +3,7 @@ import { MediaMatcher } from '@angular/cdk/layout';
 import { HttpClient } from '@angular/common/http';
 import { LANG } from './translate.component';
 import { NotificationService } from './notification.service';
-import { MatDialog } from '@angular/material';
+import { FormBuilder, FormGroup, Validators, ValidationErrors, AbstractControl, ValidatorFn } from '@angular/forms';
 
 declare function $j(selector: any): any;
 
@@ -18,29 +18,61 @@ declare var angularGlobals: any;
 export class PasswordModificationComponent implements OnInit {
 
     private _mobileQueryListener: () => void;
-    mobileQuery                 : MediaQueryList;
+    mobileQuery: MediaQueryList;
 
-    coreUrl         : string;
-    lang            : any = LANG;
-    loading         : boolean = false;
+    coreUrl: string;
+    ruleText: string = '';
+    OtherRuleText: string;
+    lang: any = LANG;
+    loading: boolean = false;
+    user: any = {};
+    hidePassword: Boolean = true;
 
-    passwordModel   : any = {
-        currentPassword : "",
-        newPassword     : "",
-        reNewPassword   : "",
+    passLength: any = false;
+    passwordRules: any = {
+        minLength: { enabled: false, value: 0 },
+        complexityUpper: { enabled: false, value: 0 },
+        complexityNumber: { enabled: false, value: 0 },
+        complexitySpecial: { enabled: false, value: 0 },
+        renewal: { enabled: false, value: 0 },
     };
 
-    constructor(changeDetectorRef: ChangeDetectorRef, media: MediaMatcher, public http: HttpClient, private notify: NotificationService, public dialog: MatDialog) {
+    passwordModel: any = {
+        currentPassword: "",
+        newPassword: "",
+        reNewPassword: "",
+    };
+    arrValidator: any[] = [];
+    validPassword: Boolean = false;
+    isLinear = false;
+    firstFormGroup: FormGroup;
+    secondFormGroup: FormGroup;
+
+    constructor(changeDetectorRef: ChangeDetectorRef, media: MediaMatcher, public http: HttpClient, private notify: NotificationService, private _formBuilder: FormBuilder) {
         $j("link[href='merged_css.php']").remove();
         this.mobileQuery = media.matchMedia('(max-width: 768px)');
         this._mobileQueryListener = () => changeDetectorRef.detectChanges();
         this.mobileQuery.addListener(this._mobileQueryListener);
+        this.user = angularGlobals.user;
+    }
+
+    regexValidator(regex: RegExp, error: ValidationErrors): ValidatorFn {
+        return (control: AbstractControl): { [key: string]: any } => {
+            if (!control.value) {
+                return null;
+            }
+            const valid = regex.test(control.value);
+            return valid ? null : error;
+        };
     }
 
     prepare() {
+        $j("link[href='merged_css.php']").remove();
+        //$j('#header').remove();
+        $j('#footer').remove();
         $j('#inner_content').remove();
-        $j('#inner_content_contact').parent('div').remove(); 
-        $j('#inner_content_contact').remove(); 
+        $j('#inner_content_contact').parent('div').remove();
+        $j('#inner_content_contact').remove();
         $j('#menunav').hide();
         $j('#divList').remove();
         $j('#magicContactsTable').remove();
@@ -55,6 +87,73 @@ export class PasswordModificationComponent implements OnInit {
     ngOnInit(): void {
         this.prepare();
         this.coreUrl = angularGlobals.coreUrl;
+        this.http.get(this.coreUrl + 'rest/passwordRules')
+            .subscribe((data: any) => {
+                let ruleTextArr: String[] = [];
+                data.rules.forEach((rule: any) => {
+
+
+                    if (rule.label == 'minLength') {
+                        this.passwordRules.minLength.enabled = rule.enabled;
+                        this.passwordRules.minLength.value = rule.value;
+                        ruleTextArr.push(rule.value + ' ' + this.lang['password' + rule.label]);
+
+                    } else if (rule.label == 'complexityUpper') {
+                        this.passwordRules.complexityUpper.enabled = rule.enabled;
+                        this.passwordRules.complexityUpper.value = rule.value;
+                        ruleTextArr.push(this.lang['password' + rule.label]);
+
+                    } else if (rule.label == 'complexityNumber') {
+                        this.passwordRules.complexityNumber.enabled = rule.enabled;
+                        this.passwordRules.complexityNumber.value = rule.value;
+                        ruleTextArr.push(this.lang['password' + rule.label]);
+
+                    } else if (rule.label == 'complexitySpecial') {
+                        this.passwordRules.complexitySpecial.enabled = rule.enabled;
+                        this.passwordRules.complexitySpecial.value = rule.value;
+                        ruleTextArr.push(this.lang['password' + rule.label]);
 
+                    } else if (rule.label == 'renewal') {
+                        this.passwordRules.renewal.enabled = rule.enabled;
+                        this.passwordRules.renewal.value = rule.value;
+                        this.OtherRuleText = this.lang['password' + rule.label] + ' <b>' + rule.value + ' ' + this.lang.days + '</b>. ' + this.lang['password2' + rule.label];
+                    }
+
+                });
+                this.ruleText = ruleTextArr.join(', ');
+            }, (err) => {
+                this.notify.error(err.error.errors);
+            });
+
+        this.firstFormGroup = this._formBuilder.group({
+            firstCtrl: [
+                '',
+                Validators.compose([Validators.minLength(6), this.regexValidator(new RegExp('[A-Z]'), { 'complexityUpper': '' }), this.regexValidator(new RegExp('[0-9]'), { 'complexityNumber': '' }), this.regexValidator(new RegExp('[^A-Za-z0-9]'), { 'complexitySpecial': '' })])
+            ]
+        });
+        //console.log(this.passwordRules);
+    }
+
+    getErrorMessage() {
+        //console.log(this.firstFormGroup.controls['firstCtrl'].errors);
+        if (this.firstFormGroup.controls['firstCtrl'].hasError('required')) {
+            return 'Champ requis !';
+
+        } else if (this.firstFormGroup.controls['firstCtrl'].hasError('minlength') && this.passwordRules.minLength.enabled) {
+            return this.passwordRules.minLength.value + ' ' + this.lang.passwordminLength + ' !';
+
+        } else if (this.firstFormGroup.controls['firstCtrl'].errors != null && this.firstFormGroup.controls['firstCtrl'].errors.complexityUpper !== undefined && this.passwordRules.complexityUpper.enabled) {
+            return this.lang.passwordcomplexityUpper + ' !';
+
+        } else if (this.firstFormGroup.controls['firstCtrl'].errors != null && this.firstFormGroup.controls['firstCtrl'].errors.complexityNumber !== undefined && this.passwordRules.complexityNumber.enabled) {
+            return this.lang.passwordcomplexityNumber + ' !';
+
+        } else if (this.firstFormGroup.controls['firstCtrl'].errors != null && this.firstFormGroup.controls['firstCtrl'].errors.complexitySpecial !== undefined && this.passwordRules.complexitySpecial.enabled) {
+            return this.lang.passwordcomplexitySpecial + ' !';
+
+        } else {
+            this.validPassword = true;
+            return '';
+        }
     }
 }
diff --git a/apps/maarch_entreprise/js/angular/lang/lang-fr.ts b/apps/maarch_entreprise/js/angular/lang/lang-fr.ts
index 9a54c12c066..1c146059d64 100755
--- a/apps/maarch_entreprise/js/angular/lang/lang-fr.ts
+++ b/apps/maarch_entreprise/js/angular/lang/lang-fr.ts
@@ -579,5 +579,12 @@ export const LANG_FR = {
     "askRedirectBasketBeforeAbsence"    : "Voulez-vous rediriger vos bannettes avant de vous mettre en absence ?",
     "confirmToBeAbsent"                 : "Voulez-vous vraiment activer votre absence ? Vous serez automatiquement déconnecté",
     "confirmDeleteMailSignature"        : "Voulez-vous vraiment supprimer la signature de mail ?",
-    "confirmDeleteSignature"            : "Voulez-vous vraiment supprimer la signature ?"
+    "confirmDeleteSignature"            : "Voulez-vous vraiment supprimer la signature ?",
+    "passwordminLength"                 : "charactère(s) au minimum",
+    "passwordcomplexityUpper"           : "1 majuscule au minimum",
+    "passwordcomplexityNumber"          : "1 chiffre au minimum",
+    "passwordcomplexitySpecial"         : "1 charactère spécial au minimum",
+    "passwordrenewal"                   : "Veuillez notez que ce nouveau mot de passe ne sera valide que",
+    "password2renewal"                  : "Passé ce délai, vous devrez en choisir un nouveau.",
+    "days"                              : "jour(s)",
 };
-- 
GitLab