From 751176d245508c539204eb6509404ea590310a4e Mon Sep 17 00:00:00 2001 From: Haris Papadopoulos Date: Mon, 27 Mar 2017 23:17:17 +0300 Subject: [PATCH] parent-form. WIP: User experience. Show controls depending on email state --- drupal/modules/epal/epal.module | 14 ++ drupal/modules/epal/epal.routing.yml | 20 ++- .../epal/src/Controller/CurrentUser.php | 141 +++++++++++++++++- drupal/modules/epal/src/Entity/EpalUsers.php | 25 ++++ .../oauthost/src/Controller/CBController.php | 2 +- .../student-application-form/parent.form.html | 94 ++++++++++++ .../student-application-form/parent.form.ts | 122 ++++++++++----- .../sector.fields.select.ts | 2 +- source/constants.ts | 2 +- source/services/helper-data-service.ts | 53 +++++++ 10 files changed, 427 insertions(+), 48 deletions(-) create mode 100644 source/components/student-application-form/parent.form.html diff --git a/drupal/modules/epal/epal.module b/drupal/modules/epal/epal.module index 94ae808..a859c2f 100644 --- a/drupal/modules/epal/epal.module +++ b/drupal/modules/epal/epal.module @@ -34,3 +34,17 @@ function epal_theme() { ], ]; } + +function epal_mail($key, &$message, $params) { + $options = array( + 'langcode' => $message['langcode'], + ); + + switch ($key) { + case 'send_verification_code': + $message['from'] = \Drupal::config('system.site')->get('mail'); + $message['subject'] = t('Email Confirmation Needed', $options); + $message['body'][] = $params['message']; + break; + } +} diff --git a/drupal/modules/epal/epal.routing.yml b/drupal/modules/epal/epal.routing.yml index ad4b40d..e66002b 100644 --- a/drupal/modules/epal/epal.routing.yml +++ b/drupal/modules/epal/epal.routing.yml @@ -1,4 +1,20 @@ -user_data: +epal.user.send_verification_code: + path: '/epal/user/sendvercode' + options: + _auth: [ 'basic_auth' ] + defaults: + _controller: '\Drupal\epal\Controller\CurrentUser::sendVerificationCode' + requirements: + _user_is_logged_in: 'TRUE' +epal.user.verify_verification_code: + path: '/epal/user/verifyvercode' + options: + _auth: [ 'basic_auth' ] + defaults: + _controller: '\Drupal\epal\Controller\CurrentUser::verifyVerificationCode' + requirements: + _user_is_logged_in: 'TRUE' +epal.user.get_data: path: '/epal/userdata' options: _auth: [ 'basic_auth' ] @@ -6,7 +22,7 @@ user_data: _controller: '\Drupal\epal\Controller\CurrentUser::getEpalUserData' requirements: _user_is_logged_in: 'TRUE' -current_user: +epal.current_user: path: '/epal/curuser' options: _auth: [ 'basic_auth' ] diff --git a/drupal/modules/epal/src/Controller/CurrentUser.php b/drupal/modules/epal/src/Controller/CurrentUser.php index 40ca67a..5593917 100644 --- a/drupal/modules/epal/src/Controller/CurrentUser.php +++ b/drupal/modules/epal/src/Controller/CurrentUser.php @@ -8,20 +8,32 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Drupal\Core\Controller\ControllerBase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Drupal\Core\Database\Connection; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; class CurrentUser extends ControllerBase { protected $entityTypeManager; + protected $logger; + protected $connection; - public function __construct(EntityTypeManagerInterface $entityTypeManager) + public function __construct( + EntityTypeManagerInterface $entityTypeManager, + Connection $connection, + LoggerChannelFactoryInterface $loggerChannel + ) { $this->entityTypeManager = $entityTypeManager; + $this->connection = $connection; + $this->logger = $loggerChannel->get('epal'); } public static function create(ContainerInterface $container) { return new static( - $container->get('entity_type.manager') + $container->get('entity_type.manager'), + $container->get('database'), + $container->get('logger.factory') ); } @@ -62,6 +74,7 @@ class CurrentUser extends ControllerBase 'userFathername' => mb_substr($epalUser->fathername->value,0,4,'UTF-8') !== "####" ? $epalUser->fathername->value : '', 'userMothername' => mb_substr($epalUser->mothername->value,0,4,'UTF-8') !== "####" ? $epalUser->mothername->value : '', 'userEmail' => mb_substr($user->mail->value,0,4,'UTF-8') !== "####" ? $user->mail->value : '', + 'verificationCodeVerified' => $epalUser->verificationcodeverified->value, ], Response::HTTP_OK); } else { return $this->respondWithStatus([ @@ -76,6 +89,130 @@ class CurrentUser extends ControllerBase } } + public function sendVerificationCode(Request $request) + { + + if (!$request->isMethod('POST')) { + return $this->respondWithStatus([ + "message" => t("Method Not Allowed") + ], Response::HTTP_METHOD_NOT_ALLOWED); + } + $authToken = $request->headers->get('PHP_AUTH_USER'); + + $trx = $this->connection->startTransaction(); + try { + $epalUsers = $this->entityTypeManager->getStorage('epal_users')->loadByProperties(array('authtoken' => $authToken)); + $epalUser = reset($epalUsers); + if ($epalUser) { + $user = $this->entityTypeManager->getStorage('user')->load($epalUser->user_id->target_id); + if ($user) { + $postData = null; + if ($content = $request->getContent()) { + $postData = json_decode($content); + $verificationCode = uniqid(); + $epalUser->set('verificationcode', $verificationCode); + $epalUser->set('verificationcodeverified', FALSE); + $epalUser->save(); + $user->set('mail', $postData->userEmail); + $user->save(); + $this->sendEmailWithVerificationCode($postData->userEmail, $verificationCode, $user); + return $this->respondWithStatus([ + 'userEmail' => $postData->userEmail, + 'verCode' => $verificationCode, + ], Response::HTTP_OK); + } + else { + return $this->respondWithStatus([ + 'message' => t("post with no data"), + ], Response::HTTP_BAD_REQUEST); + } + + } else { + return $this->respondWithStatus([ + 'message' => t("user not found"), + ], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + } else { + return $this->respondWithStatus([ + 'message' => t("EPAL user not found"), + ], Response::HTTP_FORBIDDEN); + } + } catch (\Exception $ee) { + $this->logger->warning($ee->getMessage()); + $trx->rollback(); + return false; + } + + } + + private function sendEmailWithVerificationCode($email, $vc, $user) { + $mailManager = \Drupal::service('plugin.manager.mail'); + + $module = 'epal'; + $key = 'send_verification_code'; + $to = $email; + $params['message'] = 'verification code=' . $vc; + $langcode = $user->getPreferredLangcode(); + $send = true; + + $mail_sent = $mailManager->mail($module, $key, $to, $langcode, $params, NULL, $send); + + if ($mail_sent) { + $this->logger->info("Mail Sent successfully."); + } + else { + $this->logger->info("There is error in sending mail."); + } + return; + } + + + public function verifyVerificationCode(Request $request) + { + + if (!$request->isMethod('POST')) { + return $this->respondWithStatus([ + "message" => t("Method Not Allowed") + ], Response::HTTP_METHOD_NOT_ALLOWED); + } + $authToken = $request->headers->get('PHP_AUTH_USER'); + + $epalUsers = $this->entityTypeManager->getStorage('epal_users')->loadByProperties(array('authtoken' => $authToken)); + $epalUser = reset($epalUsers); + if ($epalUser) { + + $user = $this->entityTypeManager->getStorage('user')->load($epalUser->user_id->target_id); + if ($user) { + $postData = null; + if ($content = $request->getContent()) { + $postData = json_decode($content); + if ($epalUser->verificationcode->value !== $postData->verificationCode) { + return $this->respondWithStatus([ + 'userEmail' => $user->mail->value, + 'verificationCodeVerified' => false + ], Response::HTTP_OK); + } else { + $epalUser->set('verificationcodeverified', true); + $epalUser->save(); + return $this->respondWithStatus([ + 'userEmail' => $user->mail->value, + 'verificationCodeVerified' => true + ], Response::HTTP_OK); + } + } + } else { + return $this->respondWithStatus([ + 'message' => t("user not found"), + ], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + } else { + return $this->respondWithStatus([ + 'message' => t("EPAL user not found"), + ], Response::HTTP_FORBIDDEN); + } + } private function respondWithStatus($arr, $s) { $res = new JsonResponse($arr); diff --git a/drupal/modules/epal/src/Entity/EpalUsers.php b/drupal/modules/epal/src/Entity/EpalUsers.php index 6c48516..95d510b 100644 --- a/drupal/modules/epal/src/Entity/EpalUsers.php +++ b/drupal/modules/epal/src/Entity/EpalUsers.php @@ -473,6 +473,31 @@ class EpalUsers extends ContentEntityBase implements EpalUsersInterface { ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE); + $fields['verificationcode'] = BaseFieldDefinition::create('string') + ->setLabel(t('Email Verification Code')) + ->setDescription(t('Generated email verification code')) + ->setSettings(array( + 'max_length' => 20, + 'text_processing' => 0, + )) + ->setDefaultValue('') + ->setDisplayOptions('view', array( + 'label' => 'above', + 'type' => 'string', + 'weight' => -4, + )) + ->setDisplayOptions('form', array( + 'type' => 'string_textfield', + 'weight' => -4, + )) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayConfigurable('view', TRUE); + + $fields['verificationcodeverified'] = BaseFieldDefinition::create('boolean') + ->setLabel(t('Email Verification Code Verified')) + ->setDescription(t('A boolean indicating whether the email verification code was verified.')) + ->setDefaultValue(FALSE); + $fields['accesstoken'] = BaseFieldDefinition::create('string') ->setLabel(t('Access-Token από taxis')) ->setDescription(t('Access-Token από taxis.')) diff --git a/drupal/modules/oauthost/src/Controller/CBController.php b/drupal/modules/oauthost/src/Controller/CBController.php index 4585b97..6c8961f 100644 --- a/drupal/modules/oauthost/src/Controller/CBController.php +++ b/drupal/modules/oauthost/src/Controller/CBController.php @@ -216,7 +216,7 @@ class CBController extends ControllerBase $this->logger->warning($e->getMessage()); $trx->rollback(); return false; - } catch (Exception $ee) { + } catch (\Exception $ee) { $this->logger->warning($ee->getMessage()); $trx->rollback(); return false; diff --git a/source/components/student-application-form/parent.form.html b/source/components/student-application-form/parent.form.html new file mode 100644 index 0000000..4074b12 --- /dev/null +++ b/source/components/student-application-form/parent.form.html @@ -0,0 +1,94 @@ +
+
+ +
+ +
+
+ Το πεδίο δεν μπορεί να αφεθεί κενό! +
+
+ Πληκτρολογήστε ένα σωστό συντακτικά email! +
+
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ + +
+ +
+
+ Το πεδίο δεν μπορεί να αφεθεί κενό! +
+
+ Πληκτρολογήστε το όνομά σας! +
+ +
+ +
+
+ Το πεδίο δεν μπορεί να αφεθεί κενό! +
+
+ Πληκτρολογήστε το επώνυμό σας! +
+ +
+ +
+
+ Το πεδίο δεν μπορεί να αφεθεί κενό! +
+
+ Πληκτρολογήστε το όνομα του πατέρα σας! +
+ +
+ +
+
+ Το πεδίο δεν μπορεί να αφεθεί κενό! +
+
+ Πληκτρολογήστε το όνομα της μητέρας σας! +
+ +
+
+ +
+
+
diff --git a/source/components/student-application-form/parent.form.ts b/source/components/student-application-form/parent.form.ts index b12760e..968aaed 100644 --- a/source/components/student-application-form/parent.form.ts +++ b/source/components/student-application-form/parent.form.ts @@ -1,7 +1,7 @@ -import { Component, OnInit, OnDestroy, Injectable } from '@angular/core'; +import { Component, OnInit, OnDestroy, Injectable, ViewChild, ElementRef, Renderer } from '@angular/core'; import { Router } from '@angular/router'; import { BehaviorSubject, Subscription } from 'rxjs/Rx'; -import { VALID_EMAIL_PATTERN } from '../../constants'; +import { VALID_EMAIL_PATTERN, VALID_NAMES_PATTERN } from '../../constants'; import { HelperDataService } from '../../services/helper-data-service'; import { @@ -15,60 +15,100 @@ import {AppSettings} from '../../app.settings'; @Component({ selector: 'parent-form', - template: ` -
-
- -
- -
-
- Το πεδίο δεν μπορεί να αφεθεί κενό! -
-
- Πληκτρολογήστε ένα σωστό συντακτικά email! -
-
-
- -
-
-
- -
- -
-
- -
-
-
- ` + templateUrl: 'parent.form.html' }) -@Injectable() export default class ParentForm implements OnInit { +@Injectable() export default class ParentForm implements OnInit, OnDestroy { public formGroup: FormGroup; private respond: any; private epalUserData$: BehaviorSubject; private epalUserDataSub: Subscription; + private userEmailSub: Subscription; + private showSendVerification: BehaviorSubject; + private verificationCodeSent: BehaviorSubject; + private verificationCodeVerified: BehaviorSubject; + private userEmailEnabled: BehaviorSubject; + @ViewChild('userEmail') userEmail: ElementRef; constructor(private fb: FormBuilder, private router: Router, - private hds: HelperDataService) { - this.epalUserData$ = new BehaviorSubject({}); - this.formGroup = this.fb.group({ - name: ['', [Validators.pattern(VALID_EMAIL_PATTERN),Validators.required]], - }); - }; + private hds: HelperDataService, + private rd: Renderer) { + this.showSendVerification = new BehaviorSubject(false); + this.verificationCodeSent = new BehaviorSubject(false); + this.verificationCodeVerified = new BehaviorSubject(false); + this.userEmailEnabled = new BehaviorSubject(false); + this.formGroup = this.fb.group({ + userName: ['', [Validators.pattern(VALID_NAMES_PATTERN),Validators.required]], + userSurname: ['', [Validators.pattern(VALID_NAMES_PATTERN),Validators.required]], + userFathername: ['', [Validators.pattern(VALID_NAMES_PATTERN),Validators.required]], + userMothername: ['', [Validators.pattern(VALID_NAMES_PATTERN),Validators.required]], + userEmail: [{value: '', disabled: true}, [Validators.pattern(VALID_EMAIL_PATTERN),Validators.required]], + verificationCode: ['', [Validators.required]] + }); + this.epalUserData$ = new BehaviorSubject({}); + } ngOnInit() { - this.epalUserDataSub = this.hds.getEpalUserData().subscribe(this.epalUserData$); + // this.epalUserDataSub = this.hds.getEpalUserData().subscribe(this.epalUserData$); + + this.epalUserDataSub = this.hds.getEpalUserData().subscribe( + x => { + this.epalUserData$.next(x); + + if (typeof(x.verificationCodeVerified) !== 'undefined' && x.verificationCodeVerified === "1") { + this.verificationCodeVerified.next(true); + } + + if (typeof(x.userEmail) !== 'undefined' && x.userEmail.length > 0) + this.userEmailEnabled.next(false); + else + this.userEmailEnabled.next(true); + } + ); + + + this.userEmailSub = this.formGroup.controls['userEmail'].valueChanges.subscribe( + x => { + if (this.formGroup.controls['userEmail'].value === '') { + this.enableUserEmail(); + } + } + ); + } + + ngOnDestroy() { + if (this.epalUserDataSub) this.epalUserDataSub.unsubscribe(); + if (this.userEmailSub) this.epalUserDataSub.unsubscribe(); + } + + sendVerificationCode() { + this.hds.sendVerificationCode(this.formGroup.value.userEmail) + .then(res => {this.verificationCodeSent.next(true); this.showSendVerification.next(false);}) + .catch(err => {console.log(err)}); + } + + + verifyVerificationCode() { + this.hds.verifyVerificationCode(this.formGroup.value.verificationCode) + .then(res => {this.verificationCodeSent.next(true); this.showSendVerification.next(false); this.verificationCodeVerified.next((res).verificationCodeVerified === "1" ? true : false); this.formGroup.value.userEmail=(res).userEmail;}) + .catch(err => {console.log(err)}); } verifyCodeAndContinue() { this.router.navigate(['/epal-class-select']); } + + enableUserEmail() { + this.userEmailEnabled.next(true); + this.formGroup.controls["userEmail"].enable({emitEvent: false}); + this.rd.invokeElementMethod(this.userEmail.nativeElement,'focus'); + } + + disableUserEmail() { + this.userEmailEnabled.next(false); + this.formGroup.controls["userEmail"].setValue(this.epalUserData$.getValue().userEmail); + this.formGroup.controls["userEmail"].disable({emitEvent: false}); + } } diff --git a/source/components/student-application-form/sector.fields.select.ts b/source/components/student-application-form/sector.fields.select.ts index 7827599..9d1cb21 100644 --- a/source/components/student-application-form/sector.fields.select.ts +++ b/source/components/student-application-form/sector.fields.select.ts @@ -50,7 +50,7 @@ import {AppSettings} from '../../app.settings'; ` }) -@Injectable() export default class SectorFieldsSelect implements OnInit { +@Injectable() export default class SectorFieldsSelect implements OnInit, OnDestroy { private sectorFields$: BehaviorSubject; private sectorFieldsSub: Subscription; public formGroup: FormGroup; diff --git a/source/constants.ts b/source/constants.ts index f16bb0f..2c9d0f5 100644 --- a/source/constants.ts +++ b/source/constants.ts @@ -36,4 +36,4 @@ export const VALID_NAMES_PATTERN = '[A-Za-zΑ-ΩΆΈΉΊΎΌΏα-ωάέήίύό export const VALID_ADDRESS_PATTERN = '[0-9A-Za-zΑ-ΩΆΈΉΊΎΌΏα-ωάέήίύόώ ]*$'; export const VALID_ADDRESSTK_PATTERN = '[0-9 ]*$'; export const VALID_DIGITS_PATTERN = '[0-9]*$'; -export const VALID_EMAIL_PATTERN = "/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i"; +export const VALID_EMAIL_PATTERN = '[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}'; diff --git a/source/services/helper-data-service.ts b/source/services/helper-data-service.ts index f4c04b6..0c4b93c 100644 --- a/source/services/helper-data-service.ts +++ b/source/services/helper-data-service.ts @@ -53,6 +53,59 @@ export class HelperDataService { .map(response => response.json()); }; + sendVerificationCode(email) { + this.loginInfo$.forEach(loginInfoToken => { + this.authToken = loginInfoToken.get(0).auth_token; + }); + let headers = new Headers({ + "Content-Type": "application/json", +// "Accept": "*/*", +// "Access-Control-Allow-Credentials": "true", + }); + this.createAuthorizationHeader(headers); +// let options = new RequestOptions({ headers: headers, withCredentials: true }); + let options = new RequestOptions({ headers: headers }); + return new Promise((resolve, reject) => { + this.http.post(`${AppSettings.API_ENDPOINT}/epal/user/sendvercode`, {userEmail: email}, options) + .map(response => response.json()) + .subscribe(data => { + resolve(data); + }, // put the data returned from the server in our variable + error => { + console.log("Error Sending Verification Code"); // in case of failure show this message + reject("Error Sending Verification Code"); + }, + () => console.log("Sending Verification Code"));//run this code in all cases); */ + }); + } + + verifyVerificationCode(verificationCode) { + this.loginInfo$.forEach(loginInfoToken => { + this.authToken = loginInfoToken.get(0).auth_token; + }); + let headers = new Headers({ + "Content-Type": "application/json", +// "Accept": "*/*", +// "Access-Control-Allow-Credentials": "true", + }); + this.createAuthorizationHeader(headers); +// let options = new RequestOptions({ headers: headers, withCredentials: true }); + let options = new RequestOptions({ headers: headers }); + return new Promise((resolve, reject) => { + console.log("verificationCode=" + verificationCode); + this.http.post(`${AppSettings.API_ENDPOINT}/epal/user/verifyvercode`, {verificationCode: verificationCode}, options) + .map(response => response.json()) + .subscribe(data => { + resolve(data); + }, // put the data returned from the server in our variable + error => { + console.log("Error Verifying Verification Code"); // in case of failure show this message + reject("Error Verifying Verification Code"); + }, + () => console.log("Verifying Verification Code"));//run this code in all cases); */ + }); + } + getCourseFields() { this.loginInfo$.forEach(loginInfoToken => { -- GitLab