Commit 2e3ff333 authored by Alex ORLUC's avatar Alex ORLUC
Browse files

Merge branch 'develop' into 'master'

Develop

See merge request maarch/MaarchParapheur!64
parents 36fc0c42 471bb61f
......@@ -521,6 +521,9 @@
"noConnector": "The associated connector does not exist",
"otpVisaUser": "The user will be notified by <b> email </b> at the time of his turn in the workflow.",
"messageSaved": "Message saved",
"manage_customizationAdmin": "Customize the login page"
"manage_customizationAdmin": "Customize the login page",
"internalUserOtpMsg": "The other signature modes will no longer be available for <b> {{user}} </b> after validation.",
"external": "External",
"otpSignaturePositionMandatory": "The signature position for external users is mandatory."
}
}
\ No newline at end of file
......@@ -480,8 +480,8 @@
"phoneAlt": "Mobile",
"sms": "SMS",
"source": "Source",
"otp_visa_yousignUser": "viseur (yousign)",
"otp_sign_yousignUser": "signataire (yousign)",
"otp_visa_yousignUser": "Viseur (Yousign)",
"otp_sign_yousignUser": "Signataire (Yousign)",
"role": "Role",
"otpMsg": "L'utilisateur sera notifié par <b>courriel</b> et recevra un <b>code de sécurité</b> par <b>{{security}}</b> au moment de son tour dans le circuit.",
"manage_otp_connectorsAdmin": "Administrer les connecteurs OTP",
......@@ -520,9 +520,9 @@
"otpVisaUser": "L'utilisateur sera notifié par <b>courriel</b> au moment de son tour dans le circuit.",
"messageSaved": "Message enregistré",
"manage_customizationAdmin": "Personnaliser la page de connexion",
"internalUserOtpMsg": "<b>{{user}}</b> sera converti en utilisateur externe, vous ne pourrez plus choisir les autres modes de signature.",
"internalUserOtpMsg": "Les autres modes de signatures ne seront plus disponibles pour <b>{{user}}</b> après validation.",
"externalUser": "Role externe",
"visa_yousignUser": "Viseur (Yousign)",
"sign_yousignUser": "Signataire (Yousign)"
"external": "Externe",
"otpSignaturePositionMandatory": "La position de signature pour les utilisateurs externes est obligatoire."
}
}
......@@ -369,8 +369,8 @@ class ConfigurationController
if (empty($body)) {
return $response->withStatus(400)->withJson(['errors' => 'Body is not set or empty']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['loginMessage'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body loginMessage is empty or not a string']);
} elseif (!Validator::stringType()->validate($body['loginMessage'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body loginMessage is not a string']);
}
$configuration = ConfigurationModel::getByIdentifier(['identifier' => 'customization']);
......
......@@ -365,6 +365,9 @@ class DocumentController
if ($hasElectronicSignature && $workflow['signatureMode'] == 'stamp' && $workflow['mode'] == 'sign') {
return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] signatureMode cannot be stamp after an electronic signature", 'lang' => 'stampInTheMiddleImpossible']);
}
if ($workflow['externalInformations']['role'] == 'sign_yousign' && empty($workflow['signaturePositions'])) {
return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] signaturePositions must be set for sign_yousign role"]);
}
if (!empty($workflow['signaturePositions'])) {
if (!Validator::arrayType()->validate($workflow['signaturePositions'])) {
return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] signaturePositions is not an array"]);
......
......@@ -267,19 +267,25 @@ class EmailController
public static function sendNotificationToNextUserInWorkflow(array $args)
{
$nextUser = UserModel::getById(['select' => ['email', 'preferences', 'substitute'], 'id' => $args['recipientId']]);
EmailController::sendNotificationToUser(['documentId' => $args['documentId'], 'senderId' => $args['senderId'], 'email' => $nextUser['email'], 'preferences' => $nextUser['preferences']]);
if (!empty($nextUser['substitute'])) {
$nextUser = UserModel::getById(['select' => ['email', 'preferences'], 'id' => $nextUser['substitute']]);
$nextSubstituteUser = UserModel::getById(['select' => ['email', 'preferences'], 'id' => $nextUser['substitute']]);
EmailController::sendNotificationToUser(['documentId' => $args['documentId'], 'senderId' => $args['senderId'], 'email' => $nextSubstituteUser['email'], 'preferences' => $nextSubstituteUser['preferences']]);
}
}
$nextUser['preferences'] = json_decode($nextUser['preferences'], true);
if ($nextUser['preferences']['notifications']) {
$lang = LanguageController::get(['lang' => $nextUser['preferences']['lang']]);
public static function sendNotificationToUser(array $args)
{
$args['preferences'] = json_decode($args['preferences'], true);
if ($args['preferences']['notifications']) {
$lang = LanguageController::get(['lang' => $args['preferences']['lang']]);
$url = UrlController::getCoreUrl() . 'dist/documents/' . $args['documentId'];
EmailController::createEmail([
'userId' => $args['senderId'],
'data' => [
'sender' => 'Notification',
'recipients' => [$nextUser['email']],
'recipients' => [$args['email']],
'subject' => $lang['notificationDocumentAddedSubject'],
'body' => $lang['notificationDocumentAddedBody'] . '<a href="' . $url . '">'.$url.'</a>' . $lang['notificationFooter'],
'isHtml' => true
......
......@@ -79,26 +79,28 @@ class UserController
public function getById(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']);
}
if (!Validator::intVal()->notEmpty()->validate($args['id'])) {
return $response->withStatus(400)->withJson(['errors' => 'Route id is not an integer']);
}
$user = UserController::getUserInformationsById(['id' => $args['id']]);
if ($GLOBALS['id'] == $args['id'] || PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
$user = UserController::getUserInformationsById(['id' => $args['id']]);
} else {
$user = UserModel::getById(['select' => ['id', 'firstname', 'lastname', 'email', 'phone'], 'id' => $args['id']]);
}
if (empty($user)) {
return $response->withStatus(400)->withJson(['errors' => 'User does not exist']);
}
$user['groups'] = [];
$userGroups = UserGroupModel::get(['select' => ['group_id'], 'where' => ['user_id = ?'], 'data' => [$args['id']]]);
$groupsIds = array_column($userGroups, 'group_id');
if (!empty($groupsIds)) {
$groups = GroupModel::get(['select' => ['label'], 'where' => ['id in (?)'], 'data' => [$groupsIds]]);
$user['groups'] = $groups;
if ($GLOBALS['id'] == $args['id'] || PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
$user['groups'] = [];
$userGroups = UserGroupModel::get(['select' => ['group_id'], 'where' => ['user_id = ?'], 'data' => [$args['id']]]);
$groupsIds = array_column($userGroups, 'group_id');
if (!empty($groupsIds)) {
$groups = GroupModel::get(['select' => ['label'], 'where' => ['id in (?)'], 'data' => [$groupsIds]]);
$user['groups'] = $groups;
}
}
HistoryController::add([
......
......@@ -11,7 +11,7 @@
<ion-content>
<ion-list-header>
<ion-label color="secondary">
{{ 'lang.loginMessage' | translate}} <ng-container>*</ng-container>
{{ 'lang.loginMessage' | translate}}
</ion-label>
</ion-list-header>
<ion-item lines="none">
......
......@@ -31,14 +31,14 @@ export class CustomizationComponent implements OnInit {
) { }
async ngOnInit(){
await this.getLginMessage();
await this.getLoginMessage();
}
ngOnDestroy(): void {
tinymce.remove();
}
getLginMessage() {
getLoginMessage() {
return new Promise((resolve) => {
this.http.get('../rest/authenticationInformations').pipe(
tap((data: any) => {
......@@ -59,19 +59,15 @@ export class CustomizationComponent implements OnInit {
onSubmit() {
this.loginMessage = tinymce.get('login_message').getContent();
if (this.functions.empty(this.loginMessage)) {
this.notificationService.error(this.translate.instant('lang.requiredField'));
} else {
this.http.put('../rest/customization', {id: this.authService.user.id , loginMessage: this.loginMessage}).pipe(
tap(() => {
this.notificationService.success(this.translate.instant('lang.messageSaved'));
}),
catchError((err: any) => {
this.notificationService.handleErrors(err);
return of(false);
})
).subscribe();
}
this.http.put('../rest/customization', {id: this.authService.user.id , loginMessage: this.loginMessage}).pipe(
tap(() => {
this.notificationService.success(this.translate.instant('lang.messageSaved'));
}),
catchError((err: any) => {
this.notificationService.handleErrors(err);
return of(false);
})
).subscribe();
}
initMce() {
......
......@@ -54,9 +54,9 @@
pattern="(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"></ion-input>
</ion-item>
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.phoneAlt' | translate}} *</ion-label>
<ion-input type="text" name="phone" [(ngModel)]="user.phone" required
pattern="^(?:0|\(?\+33\)?\s?|0033\s?)[1-79](?:[\.\-\s]?\d\d){4}$"></ion-input>
<ion-label color="secondary" position="floating">{{'lang.phoneAlt' | translate}}</ion-label>
<ion-input type="text" name="phone" [(ngModel)]="user.phone"
pattern="\+?((|\ |\.|\(|\)|\-)?(\d)*)*\d$"></ion-input>
</ion-item>
<ion-item>
<ion-label>{{'lang.restUser' | translate}}</ion-label>
......
......@@ -329,7 +329,7 @@ export class DocumentComponent implements OnInit {
const realUserWorkflow = this.mainDocument.workflow.filter((line: { current: boolean }) => line.current === true);
this.mainDocument.isCertified = this.mainDocument.workflow.filter((line: any) => line.status !== 'REF' && line.status !== 'STOP' && line.mode === 'sign' && line.signatureMode !== 'stamp' && line.processDate !== null).length > 0;
this.mainDocument.isCertified = this.mainDocument.workflow.filter((line: any) => line.status !== 'REF' && line.status !== 'STOP' && line.mode.indexOf('sign') > -1 && line.signatureMode !== 'stamp' && line.processDate !== null).length > 0;
const externalUser: any = this.mainDocument.workflow.filter((user: any) => user.userId === null && user.current === true);
if (realUserWorkflow.length === 0 || this.mainDocument.readOnly || externalUser.length > 0) {
this.actionsList = [
......
......@@ -26,7 +26,7 @@
<app-otp-yousign #appOtpYousign *ngIf="currentSource && currentSource.type === 'yousign'" [connectorId]="connectorId" [otpYousign]="data"></app-otp-yousign>
<ion-card>
<ion-item color="primary">
<ion-label class="info" [innerHTML]="(appOtpYousign?.getData().role !== 'visa' ? 'lang.otpMsg' : 'lang.otpVisaUser') | translate : { security : appOtpYousign?.getSecurityMode()}"></ion-label>
<ion-label class="info" [innerHTML]="(appOtpYousign?.getData().role !== 'otp_visa_yousign' ? 'lang.otpMsg' : 'lang.otpVisaUser') | translate : { security : appOtpYousign?.getSecurityMode()}"></ion-label>
</ion-item>
</ion-card>
</ion-content>
......
......@@ -35,7 +35,7 @@
</div>
</ion-radio-group>
</ion-list>
<ion-list *ngIf="securityModes.length > 1 && otp.role !== 'visa'">
<ion-list *ngIf="securityModes.length > 1 && otp.role !== 'otp_visa_yousign'">
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.securityCodeSendMode' | translate}} *</ion-label>
<ion-select name="mode" [(ngModel)]="otp.security" [value]="otp.security"
......
......@@ -20,7 +20,7 @@ export class OtpYousignComponent implements OnInit {
securityModes: any[] = [];
roles: any[] = ['visa_yousign', 'sign_yousign'];
roles: any[] = ['otp_visa_yousign', 'otp_sign_yousign'];
otp: any = {
type: 'yousign',
......@@ -29,7 +29,7 @@ export class OtpYousignComponent implements OnInit {
email: '',
phone: '',
security: 'sms',
role: 'sign_yousign',
role: 'otp_sign_yousign',
sourceId: '',
modes: this.roles
};
......@@ -42,7 +42,7 @@ export class OtpYousignComponent implements OnInit {
) {
this.otpService.catchEvent().subscribe(async (res) => {
if (res.id === 'connector') {
this.otp.sourceId = res.connectorId;
this.connectorId = res.connectorId;
await this.getConfig();
}
});
......@@ -95,10 +95,15 @@ export class OtpYousignComponent implements OnInit {
getRegexPhone() {
// map country calling code with national number length
const phonesMap = {
'32': [8, 10], // Belgium
'33': 9, // France
'1' : 10, // United States
'27': 9 // South Africa
'32': [8, 10], // Belgium
'41': [4, 12], // Swiss
'44': [7, 10], // United Kingdom
'352': [4, 11], // Luxembourg
'351': [9, 11], // Portugal
'33': 9, // France
'1' : 10, // USA
'39': 11, // Italy
'34': 9 // Spain
};
const regex = Object.keys(phonesMap).reduce((phoneFormats: any [], countryCode: any) => {
const numberLength = phonesMap[countryCode];
......
......@@ -12,6 +12,7 @@ import { NotificationService } from '../service/notification.service';
import { SignaturesContentService } from '../service/signatures.service';
import { SignaturePositionComponent } from './signature-position/signature-position.component';
import { ActionsService } from '../service/actions.service';
import { FunctionsService } from '../service/functions.service';
@Component({
templateUrl: 'indexation.component.html',
......@@ -42,7 +43,8 @@ export class IndexationComponent implements OnInit {
public alertController: AlertController,
public datePipe: DatePipe,
public modalController: ModalController,
public actionsService: ActionsService
public actionsService: ActionsService,
private functionsService: FunctionsService
) { }
ngOnInit(): void { }
......@@ -102,7 +104,7 @@ export class IndexationComponent implements OnInit {
const obj: any = await this.appVisaWorkflow.formatData(element);
element = obj;
});
}
}
resolve(true);
}),
catchError((err: any) => {
......@@ -379,12 +381,25 @@ export class IndexationComponent implements OnInit {
this.notificationService.error('lang.workflowUserstMandatory');
this.menu.open('right-menu');
return false;
}
else if (this.appVisaWorkflow.getCurrentWorkflow().filter((user: any) => user.userId === null && user.noConnector !== undefined).length > 0) {
} else if (this.appVisaWorkflow.getCurrentWorkflow().filter((user: any) => user.userId === null && user.noConnector !== undefined).length > 0) {
this.notificationService.error('lang.noConnector');
return false;
} else if (this.hasEmptyOtpSignaturePosition()) {
this.notificationService.error('lang.otpSignaturePositionMandatory');
return false;
} else {
return true;
}
}
hasEmptyOtpSignaturePosition() {
const workflow = this.formatData(null)[0].workflow;
const isSign = /_sign_/g;
const hasEmptyPosition = workflow.filter((item: any) => item.signaturePositions.length === 0 && !this.functionsService.empty(item.externalInformations) && item.mode.match(isSign) !== null).length > 0;
return hasEmptyPosition;
}
}
......@@ -2,7 +2,7 @@
<mat-icon svgIcon="maarchLogo" class="maarchLogo"></mat-icon>
<ion-card style="width: 400px;position: absolute;left: 50%;top: 50%;transform: translate(-50%,-50%);">
<ion-card-header>
<ion-card-header *ngIf="!functions.empty(authService.loginMessage)">
<ion-card-subtitle class="loginMessage">
<div [innerHTML]="authService.loginMessage | safeHtml"></div>
</ion-card-subtitle>
......
......@@ -11,6 +11,7 @@ import { tap, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import { LoadingController, MenuController } from '@ionic/angular';
import { TranslateService } from '@ngx-translate/core';
import { FunctionsService } from '../service/functions.service';
@Component({
templateUrl: 'login.component.html',
......@@ -37,6 +38,7 @@ export class LoginComponent implements OnInit, AfterViewInit {
public loadingController: LoadingController,
private translate: TranslateService,
private menu: MenuController,
public functions: FunctionsService
) { }
async ngOnInit() {
......@@ -49,7 +51,7 @@ export class LoginComponent implements OnInit, AfterViewInit {
this.environment = environment;
this.signaturesService.reset();
await this.loadCommitInformation();
await this.loadCommitInformation();
}
ionViewWillEnter() {
......
......@@ -54,7 +54,7 @@
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.phoneAlt' | translate}}</ion-label>
<ion-input name="phone" [(ngModel)]="profileInfo.phone" [readonly]="authService.authMode !== 'default'"
pattern="^(?:0|\(?\+33\)?\s?|0033\s?)[1-79](?:[\.\-\s]?\d\d){4}$" required>
pattern="\+?((|\ |\.|\(|\)|\-)?(\d)*)*\d$">
</ion-input>
</ion-item>
<div *ngIf="authService.authMode === 'default'">
......
......@@ -106,11 +106,6 @@ export class AuthGuard implements CanActivate {
'id': 'visa',
'type': 'visa',
'color': '#135F7F'
},
{
'id': 'sign',
'type': 'sign',
'color': '#135F7F'
}
];
this.authService.signatureRoles = this.authService.signatureRoles.concat(dataModes.map((item: any) => ({
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment