forked from EVOgeek/Vmeda.Online
		
	MOBILE-3592 user: Implement profile field delegate and component
This commit is contained in:
		
							parent
							
								
									fa294d7135
								
							
						
					
					
						commit
						3722126b5b
					
				| @ -36,6 +36,7 @@ import { CoreContextMenuComponent } from './context-menu/context-menu'; | ||||
| import { CoreContextMenuItemComponent } from './context-menu/context-menu-item'; | ||||
| import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover'; | ||||
| import { CoreUserAvatarComponent } from './user-avatar/user-avatar'; | ||||
| import { CoreDynamicComponent } from './dynamic-component/dynamic-component'; | ||||
| 
 | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CorePipesModule } from '@pipes/pipes.module'; | ||||
| @ -63,6 +64,7 @@ import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons'; | ||||
|         CoreContextMenuPopoverComponent, | ||||
|         CoreNavBarButtonsComponent, | ||||
|         CoreUserAvatarComponent, | ||||
|         CoreDynamicComponent, | ||||
|     ], | ||||
|     imports: [ | ||||
|         CommonModule, | ||||
| @ -92,6 +94,7 @@ import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons'; | ||||
|         CoreContextMenuPopoverComponent, | ||||
|         CoreNavBarButtonsComponent, | ||||
|         CoreUserAvatarComponent, | ||||
|         CoreDynamicComponent, | ||||
|     ], | ||||
| }) | ||||
| export class CoreComponentsModule {} | ||||
|  | ||||
| @ -0,0 +1,5 @@ | ||||
| <!-- Content to display if no dynamic component. --> | ||||
| <ng-content *ngIf="!instance"></ng-content> | ||||
| 
 | ||||
| <!-- Container of the dynamic component --> | ||||
| <ng-container #dynamicComponent></ng-container> | ||||
							
								
								
									
										198
									
								
								src/core/components/dynamic-component/dynamic-component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/core/components/dynamic-component/dynamic-component.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,198 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { | ||||
|     Component, | ||||
|     Input, | ||||
|     ViewChild, | ||||
|     OnChanges, | ||||
|     DoCheck, | ||||
|     ViewContainerRef, | ||||
|     ComponentFactoryResolver, | ||||
|     ComponentRef, | ||||
|     KeyValueDiffers, | ||||
|     SimpleChange, | ||||
|     ChangeDetectorRef, | ||||
|     Optional, | ||||
|     ElementRef, | ||||
|     KeyValueDiffer, | ||||
|     Type, | ||||
| } from '@angular/core'; | ||||
| import { NavController } from '@ionic/angular'; | ||||
| 
 | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| 
 | ||||
| /** | ||||
|  * Component to create another component dynamically. | ||||
|  * | ||||
|  * You need to pass the class of the component to this component (the class, not the name), along with the input data. | ||||
|  * | ||||
|  * So you should do something like: | ||||
|  * | ||||
|  *     import { MyComponent } from './component'; | ||||
|  * | ||||
|  *     ... | ||||
|  * | ||||
|  *         this.component = MyComponent; | ||||
|  * | ||||
|  * And in the template: | ||||
|  * | ||||
|  *     <core-dynamic-component [component]="component" [data]="data"> | ||||
|  *         <p>Cannot render the data.</p> | ||||
|  *     </core-dynamic-component> | ||||
|  * | ||||
|  * Please notice that the component that you pass needs to be declared in entryComponents of the module to be created dynamically. | ||||
|  * | ||||
|  * Alternatively, you can also supply a ComponentRef instead of the class of the component. In this case, the component won't | ||||
|  * be instantiated because it already is, it will be attached to the view and the right data will be passed to it. | ||||
|  * Passing ComponentRef is meant for site plugins, so we'll inject a NavController instance to the component. | ||||
|  * | ||||
|  * The contents of this component will be displayed if no component is supplied or it cannot be created. In the example above, | ||||
|  * if no component is supplied then the template will show the message "Cannot render the data.". | ||||
|  */ | ||||
| /* eslint-disable @angular-eslint/no-conflicting-lifecycle */ | ||||
| @Component({ | ||||
|     selector: 'core-dynamic-component', | ||||
|     templateUrl: 'core-dynamic-component.html', | ||||
| }) | ||||
| export class CoreDynamicComponent implements OnChanges, DoCheck { | ||||
| 
 | ||||
|     @Input() component?: Type<unknown>; | ||||
|     @Input() data?: Record<string | number, unknown>; | ||||
| 
 | ||||
|     // Get the container where to put the dynamic component.
 | ||||
|     @ViewChild('dynamicComponent', { read: ViewContainerRef }) set dynamicComponent(el: ViewContainerRef) { | ||||
|         this.container = el; | ||||
|         this.createComponent(); | ||||
|     } | ||||
| 
 | ||||
|     instance?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
 | ||||
|     container?: ViewContainerRef; | ||||
| 
 | ||||
|     protected logger: CoreLogger; | ||||
|     protected differ: KeyValueDiffer<unknown, unknown>; // To detect changes in the data input.
 | ||||
|     protected lastComponent?: Type<unknown>; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected factoryResolver: ComponentFactoryResolver, | ||||
|         differs: KeyValueDiffers, | ||||
|         @Optional() protected navCtrl: NavController, | ||||
|         protected cdr: ChangeDetectorRef, | ||||
|         protected element: ElementRef, | ||||
|     ) { | ||||
| 
 | ||||
|         this.logger = CoreLogger.getInstance('CoreDynamicComponent'); | ||||
|         this.differ = differs.find([]).create(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Detect changes on input properties. | ||||
|      */ | ||||
|     ngOnChanges(changes: { [name: string]: SimpleChange }): void { | ||||
|         if (changes.component && !this.component) { | ||||
|             // Component not set, destroy the instance if any.
 | ||||
|             this.lastComponent = undefined; | ||||
|             this.instance = undefined; | ||||
|             this.container?.clear(); | ||||
|         } else if (changes.component && (!this.instance || this.component != this.lastComponent)) { | ||||
|             this.createComponent(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays). | ||||
|      */ | ||||
|     ngDoCheck(): void { | ||||
|         if (this.instance) { | ||||
|             // Check if there's any change in the data object.
 | ||||
|             const changes = this.differ.diff(this.data || {}); | ||||
|             if (changes) { | ||||
|                 this.setInputData(); | ||||
|                 if (this.instance.ngOnChanges) { | ||||
|                     this.instance.ngOnChanges(CoreDomUtils.instance.createChangesFromKeyValueDiff(changes)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Call a certain function on the component. | ||||
|      * | ||||
|      * @param name Name of the function to call. | ||||
|      * @param params List of params to send to the function. | ||||
|      * @return Result of the call. Undefined if no component instance or the function doesn't exist. | ||||
|      */ | ||||
|     callComponentFunction<T = unknown>(name: string, params?: unknown[]): T | undefined { | ||||
|         if (this.instance && typeof this.instance[name] == 'function') { | ||||
|             return this.instance[name].apply(this.instance, params); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create a component, add it to a container and set the input data. | ||||
|      * | ||||
|      * @return Whether the component was successfully created. | ||||
|      */ | ||||
|     protected createComponent(): boolean { | ||||
|         this.lastComponent = this.component; | ||||
| 
 | ||||
|         if (!this.component || !this.container) { | ||||
|             // No component to instantiate or container doesn't exist right now.
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if (this.instance) { | ||||
|             // Component already instantiated.
 | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if (this.component instanceof ComponentRef) { | ||||
|             // A ComponentRef was supplied instead of the component class. Add it to the view.
 | ||||
|             this.container.insert(this.component.hostView); | ||||
|             this.instance = this.component.instance; | ||||
| 
 | ||||
|             // This feature is usually meant for site plugins. Inject some properties.
 | ||||
|             this.instance['ChangeDetectorRef'] = this.cdr; | ||||
|             this.instance['NavController'] = this.navCtrl; | ||||
|             this.instance['componentContainer'] = this.element.nativeElement; | ||||
|         } else { | ||||
|             try { | ||||
|                 // Create the component and add it to the container.
 | ||||
|                 const factory = this.factoryResolver.resolveComponentFactory(this.component); | ||||
|                 const componentRef = this.container.createComponent(factory); | ||||
| 
 | ||||
|                 this.instance = componentRef.instance; | ||||
|             } catch (ex) { | ||||
|                 this.logger.error('Error creating component', ex); | ||||
| 
 | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this.setInputData(); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the input data for the component. | ||||
|      */ | ||||
|     protected setInputData(): void { | ||||
|         for (const name in this.data) { | ||||
|             this.instance[name] = this.data[name]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -172,8 +172,8 @@ | ||||
|                 <ion-item-divider class="ion-text-wrap"> | ||||
|                     <ion-label>{{ category.name }}</ion-label> | ||||
|                 </ion-item-divider> | ||||
|                 <!-- @todo <core-user-profile-field *ngFor="let field of category.fields" [field]="field" edit="true" signup="true" | ||||
|                     registerAuth="email" [form]="signupForm"></core-user-profile-field> --> | ||||
|                 <core-user-profile-field *ngFor="let field of category.fields" [field]="field" [edit]="true" [signup]="true" | ||||
|                     registerAuth="email" [form]="signupForm"></core-user-profile-field> | ||||
|             </ng-container> | ||||
| 
 | ||||
|             <!-- ReCAPTCHA --> | ||||
|  | ||||
| @ -21,6 +21,7 @@ import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CoreUserComponentsModule } from '@features/user/components/components.module'; | ||||
| 
 | ||||
| import { CoreLoginEmailSignupPage } from './email-signup'; | ||||
| 
 | ||||
| @ -41,6 +42,7 @@ const routes: Routes = [ | ||||
|         ReactiveFormsModule, | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         CoreUserComponentsModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreLoginEmailSignupPage, | ||||
|  | ||||
| @ -25,6 +25,7 @@ import { CoreWS, CoreWSExternalWarning } from '@services/ws'; | ||||
| import { CoreConstants } from '@/core/constants'; | ||||
| import { Translate } from '@singletons'; | ||||
| import { CoreSitePublicConfigResponse } from '@classes/site'; | ||||
| import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate'; | ||||
| 
 | ||||
| import { | ||||
|     AuthEmailSignupProfileFieldsCategory, | ||||
| @ -82,6 +83,7 @@ export class CoreLoginEmailSignupPage implements OnInit { | ||||
|         protected navCtrl: NavController, | ||||
|         protected fb: FormBuilder, | ||||
|         protected route: ActivatedRoute, | ||||
|         protected userProfileFieldDelegate: CoreUserProfileFieldDelegate, | ||||
|     ) { | ||||
|         // Create the ageVerificationForm.
 | ||||
|         this.ageVerificationForm = this.fb.group({ | ||||
| @ -156,7 +158,7 @@ export class CoreLoginEmailSignupPage implements OnInit { | ||||
|                 if (typeof this.ageDigitalConsentVerification == 'undefined') { | ||||
| 
 | ||||
|                     const result = await CoreUtils.instance.ignoreErrors( | ||||
|                         CoreWS.instance.callAjax<IsAgeVerificationEnabledResponse>( | ||||
|                         CoreWS.instance.callAjax<IsAgeVerificationEnabledWSResponse>( | ||||
|                             'core_auth_is_age_digital_consent_verification_enabled', | ||||
|                             {}, | ||||
|                             { siteUrl: this.siteUrl }, | ||||
| @ -189,7 +191,11 @@ export class CoreLoginEmailSignupPage implements OnInit { | ||||
|             { siteUrl: this.siteUrl }, | ||||
|         ); | ||||
| 
 | ||||
|         // @todo userProfileFieldDelegate
 | ||||
|         if (this.userProfileFieldDelegate.hasRequiredUnsupportedField(this.settings.profilefields)) { | ||||
|             this.allRequiredSupported = false; | ||||
| 
 | ||||
|             throw new Error(Translate.instance.instant('core.login.signuprequiredfieldnotsupported')); | ||||
|         } | ||||
| 
 | ||||
|         this.categories = CoreLoginHelper.instance.formatProfileFieldsForSignup(this.settings.profilefields); | ||||
| 
 | ||||
| @ -274,7 +280,7 @@ export class CoreLoginEmailSignupPage implements OnInit { | ||||
| 
 | ||||
|         const modal = await CoreDomUtils.instance.showModalLoading('core.sending', true); | ||||
| 
 | ||||
|         const params: Record<string, unknown> = { | ||||
|         const params: SignupUserWSParams = { | ||||
|             username: this.signupForm.value.username.trim().toLowerCase(), | ||||
|             password: this.signupForm.value.password, | ||||
|             firstname: CoreTextUtils.instance.cleanTags(this.signupForm.value.firstname), | ||||
| @ -295,8 +301,15 @@ export class CoreLoginEmailSignupPage implements OnInit { | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             // @todo Get the data for the custom profile fields.
 | ||||
|             const result = await CoreWS.instance.callAjax<SignupUserResult>( | ||||
|             // Get the data for the custom profile fields.
 | ||||
|             params.customprofilefields = await this.userProfileFieldDelegate.getDataForFields( | ||||
|                 this.settings?.profilefields, | ||||
|                 true, | ||||
|                 'email', | ||||
|                 this.signupForm.value, | ||||
|             ); | ||||
| 
 | ||||
|             const result = await CoreWS.instance.callAjax<SignupUserWSResult>( | ||||
|                 'auth_email_signup_user', | ||||
|                 params, | ||||
|                 { siteUrl: this.siteUrl }, | ||||
| @ -376,7 +389,7 @@ export class CoreLoginEmailSignupPage implements OnInit { | ||||
|         params.age = parseInt(params.age, 10); // Use just the integer part.
 | ||||
| 
 | ||||
|         try { | ||||
|             const result = await CoreWS.instance.callAjax<IsMinorResult>('core_auth_is_minor', params, { siteUrl: this.siteUrl }); | ||||
|             const result = await CoreWS.instance.callAjax<IsMinorWSResult>('core_auth_is_minor', params, { siteUrl: this.siteUrl }); | ||||
| 
 | ||||
|             CoreDomUtils.instance.triggerFormSubmittedEvent(this.ageFormElement, true); | ||||
| 
 | ||||
| @ -404,14 +417,35 @@ export class CoreLoginEmailSignupPage implements OnInit { | ||||
| /** | ||||
|  * Result of WS core_auth_is_age_digital_consent_verification_enabled. | ||||
|  */ | ||||
| export type IsAgeVerificationEnabledResponse = { | ||||
| type IsAgeVerificationEnabledWSResponse = { | ||||
|     status: boolean; // True if digital consent verification is enabled, false otherwise.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Params for WS auth_email_signup_user. | ||||
|  */ | ||||
| type SignupUserWSParams = { | ||||
|     username: string; // Username.
 | ||||
|     password: string; // Plain text password.
 | ||||
|     firstname: string; // The first name(s) of the user.
 | ||||
|     lastname: string; // The family name of the user.
 | ||||
|     email: string; // A valid and unique email address.
 | ||||
|     city?: string; // Home city of the user.
 | ||||
|     country?: string; // Home country code.
 | ||||
|     recaptchachallengehash?: string; // Recaptcha challenge hash.
 | ||||
|     recaptcharesponse?: string; // Recaptcha response.
 | ||||
|     customprofilefields?: { // User custom fields (also known as user profile fields).
 | ||||
|         type: string; // The type of the custom field.
 | ||||
|         name: string; // The name of the custom field.
 | ||||
|         value: unknown; // Custom field value, can be an encoded json if required.
 | ||||
|     }[]; | ||||
|     redirect?: string; // Redirect the user to this site url after confirmation.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of WS auth_email_signup_user. | ||||
|  */ | ||||
| export type SignupUserResult = { | ||||
| type SignupUserWSResult = { | ||||
|     success: boolean; // True if the user was created false otherwise.
 | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| @ -419,6 +453,6 @@ export type SignupUserResult = { | ||||
| /** | ||||
|  * Result of WS core_auth_is_minor. | ||||
|  */ | ||||
| export type IsMinorResult = { | ||||
| type IsMinorWSResult = { | ||||
|     status: boolean; // True if the user is considered to be a digital minor, false if not.
 | ||||
| }; | ||||
|  | ||||
							
								
								
									
										43
									
								
								src/core/features/user/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/core/features/user/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { CommonModule } from '@angular/common'; | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CorePipesModule } from '@pipes/pipes.module'; | ||||
| import { CoreUserProfileFieldComponent } from './user-profile-field/user-profile-field'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
|         CoreUserProfileFieldComponent, | ||||
|     ], | ||||
|     imports: [ | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         TranslateModule.forChild(), | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         CorePipesModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|     ], | ||||
|     exports: [ | ||||
|         CoreUserProfileFieldComponent, | ||||
|     ], | ||||
| }) | ||||
| export class CoreUserComponentsModule {} | ||||
| @ -0,0 +1 @@ | ||||
| <core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component> | ||||
| @ -0,0 +1,83 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Component, Input, OnInit, Injector, Type } from '@angular/core'; | ||||
| import { FormGroup } from '@angular/forms'; | ||||
| 
 | ||||
| import { AuthEmailSignupProfileField } from '@features/login/services/login-helper'; | ||||
| import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| 
 | ||||
| /** | ||||
|  * Directive to render user profile field. | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'core-user-profile-field', | ||||
|     templateUrl: 'core-user-profile-field.html', | ||||
| }) | ||||
| export class CoreUserProfileFieldComponent implements OnInit { | ||||
| 
 | ||||
|     @Input() field?: AuthEmailSignupProfileField; // The profile field to be rendered.
 | ||||
|     @Input() signup = false; // True if editing the field in signup. Defaults to false.
 | ||||
|     @Input() edit = false; // True if editing the field. Defaults to false.
 | ||||
|     @Input() form?: FormGroup; // Form where to add the form control. Required if edit=true or signup=true.
 | ||||
|     @Input() registerAuth?: string; // Register auth method. E.g. 'email'.
 | ||||
|     @Input() contextLevel?: string; // The context level.
 | ||||
|     @Input() contextInstanceId?: number; // The instance ID related to the context.
 | ||||
|     @Input() courseId?: number; // Course ID the field belongs to (if any). It can be used to improve performance with filters.
 | ||||
| 
 | ||||
|     componentClass?: Type<unknown>; // The class of the component to render.
 | ||||
|     data: CoreUserProfileFieldComponentData = {}; // Data to pass to the component.
 | ||||
| 
 | ||||
|     constructor( | ||||
|         protected userProfileFieldsDelegate: CoreUserProfileFieldDelegate, | ||||
|         protected injector: Injector, | ||||
|     ) { } | ||||
| 
 | ||||
|     /** | ||||
|      * Component being initialized. | ||||
|      */ | ||||
|     async ngOnInit(): Promise<void> { | ||||
|         if (!this.field) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.componentClass = await this.userProfileFieldsDelegate.getComponent(this.injector, this.field, this.signup); | ||||
| 
 | ||||
|         this.data.field = this.field; | ||||
|         this.data.edit = CoreUtils.instance.isTrueOrOne(this.edit); | ||||
|         if (this.edit) { | ||||
|             this.data.signup = CoreUtils.instance.isTrueOrOne(this.signup); | ||||
|             this.data.disabled = CoreUtils.instance.isTrueOrOne(this.field.locked); | ||||
|             this.data.form = this.form; | ||||
|             this.data.registerAuth = this.registerAuth; | ||||
|             this.data.contextLevel = this.contextLevel; | ||||
|             this.data.contextInstanceId = this.contextInstanceId; | ||||
|             this.data.courseId = this.courseId; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export type CoreUserProfileFieldComponentData = { | ||||
|     field?: AuthEmailSignupProfileField; | ||||
|     edit?: boolean; | ||||
|     signup?: boolean; | ||||
|     disabled?: boolean; | ||||
|     form?: FormGroup; | ||||
|     registerAuth?: string; | ||||
|     contextLevel?: string; | ||||
|     contextInstanceId?: number; | ||||
|     courseId?: number; | ||||
| }; | ||||
| @ -41,7 +41,7 @@ | ||||
|                 <ion-item class="ion-text-wrap" *ngIf="user.address"> | ||||
|                     <ion-label> | ||||
|                         <h2>{{ 'core.user.address' | translate}}</h2> | ||||
|                         <p><a class="core-anchor" [href]="user.encodedAddress" core-link auto-login="no"> | ||||
|                         <p><a class="core-anchor" [href]="encodedAddress" core-link auto-login="no"> | ||||
|                             {{ user.address }} | ||||
|                         </a></p> | ||||
|                     </ion-label> | ||||
|  | ||||
| @ -20,6 +20,7 @@ import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| import { CoreUserComponentsModule } from '@features/user/components/components.module'; | ||||
| 
 | ||||
| import { CoreUserAboutPage } from './about.page'; | ||||
| 
 | ||||
| @ -38,6 +39,7 @@ const routes: Routes = [ | ||||
|         TranslateModule.forChild(), | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         CoreUserComponentsModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreUserAboutPage, | ||||
|  | ||||
							
								
								
									
										210
									
								
								src/core/features/user/services/user-profile-field-delegate.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								src/core/features/user/services/user-profile-field-delegate.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,210 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable, Injector, Type } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; | ||||
| import { CoreError } from '@classes/errors/error'; | ||||
| import { AuthEmailSignupProfileField } from '@features/login/services/login-helper'; | ||||
| import { CoreUserProfileField } from './user'; | ||||
| 
 | ||||
| /** | ||||
|  * Interface that all user profile field handlers must implement. | ||||
|  */ | ||||
| export interface CoreUserProfileFieldHandler extends CoreDelegateHandler { | ||||
|     /** | ||||
|      * Type of the field the handler supports. E.g. 'checkbox'. | ||||
|      */ | ||||
|     type: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Return the Component to use to display the user profile field. | ||||
|      * It's recommended to return the class of the component, but you can also return an instance of the component. | ||||
|      * | ||||
|      * @param injector Injector. | ||||
|      * @return The component (or promise resolved with component) to use, undefined if not found. | ||||
|      */ | ||||
|     getComponent(injector: Injector): Type<unknown> | Promise<Type<unknown>>; | ||||
| 
 | ||||
|     /** | ||||
|      * Get the data to send for the field based on the input data. | ||||
|      * | ||||
|      * @param field User field to get the data for. | ||||
|      * @param signup True if user is in signup page. | ||||
|      * @param registerAuth Register auth method. E.g. 'email'. | ||||
|      * @param formValues Form Values. | ||||
|      * @return Data to send for the field. | ||||
|      */ | ||||
|     getData?( | ||||
|         field: AuthEmailSignupProfileField | CoreUserProfileField, | ||||
|         signup: boolean, | ||||
|         registerAuth: string, | ||||
|         formValues: Record<string, unknown>, | ||||
|     ): Promise<CoreUserProfileFieldHandlerData>; | ||||
| } | ||||
| 
 | ||||
| export interface CoreUserProfileFieldHandlerData { | ||||
|     /** | ||||
|      * Name of the custom field. | ||||
|      */ | ||||
|     name: string; | ||||
| 
 | ||||
|     /** | ||||
|      * The type of the custom field | ||||
|      */ | ||||
|     type: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Value of the custom field. | ||||
|      */ | ||||
|     value: unknown; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Service to interact with user profile fields. | ||||
|  */ | ||||
| @Injectable({ | ||||
|     providedIn: 'root', | ||||
| }) | ||||
| export class CoreUserProfileFieldDelegate extends CoreDelegate<CoreUserProfileFieldHandler> { | ||||
| 
 | ||||
|     protected handlerNameProperty = 'type'; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super('CoreUserProfileFieldDelegate', true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the type of a field. | ||||
|      * | ||||
|      * @param field The field to get its type. | ||||
|      * @return The field type. | ||||
|      */ | ||||
|     protected getType(field: AuthEmailSignupProfileField | CoreUserProfileField): string { | ||||
|         return ('type' in field ? field.type : field.datatype) || ''; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the component to use to display an user field. | ||||
|      * | ||||
|      * @param injector Injector. | ||||
|      * @param field User field to get the directive for. | ||||
|      * @param signup True if user is in signup page. | ||||
|      * @return Promise resolved with component to use, undefined if not found. | ||||
|      */ | ||||
|     async getComponent( | ||||
|         injector: Injector, | ||||
|         field: AuthEmailSignupProfileField | CoreUserProfileField, | ||||
|         signup: boolean, | ||||
|     ): Promise<Type<unknown> | undefined> { | ||||
|         const type = this.getType(field); | ||||
| 
 | ||||
|         try { | ||||
|             if (signup) { | ||||
|                 return await this.executeFunction(type, 'getComponent', [injector]); | ||||
|             } else { | ||||
|                 return await this.executeFunctionOnEnabled(type, 'getComponent', [injector]); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             this.logger.error('Error getting component for field', type, error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the data to send for a certain field based on the input data. | ||||
|      * | ||||
|      * @param field User field to get the data for. | ||||
|      * @param signup True if user is in signup page. | ||||
|      * @param registerAuth Register auth method. E.g. 'email'. | ||||
|      * @param formValues Form values. | ||||
|      * @return Data to send for the field. | ||||
|      */ | ||||
|     async getDataForField( | ||||
|         field: AuthEmailSignupProfileField | CoreUserProfileField, | ||||
|         signup: boolean, | ||||
|         registerAuth: string, | ||||
|         formValues: Record<string, unknown>, | ||||
|     ): Promise<CoreUserProfileFieldHandlerData> { | ||||
|         const type = this.getType(field); | ||||
|         const handler = this.getHandler(type, !signup); | ||||
| 
 | ||||
|         if (handler) { | ||||
|             const name = 'profile_field_' + field.shortname; | ||||
| 
 | ||||
|             if (handler.getData) { | ||||
|                 return await handler.getData(field, signup, registerAuth, formValues); | ||||
|             } else if (field.shortname && typeof formValues[name] != 'undefined') { | ||||
|                 // Handler doesn't implement the function, but the form has data for the field.
 | ||||
|                 return { | ||||
|                     type: type, | ||||
|                     name: name, | ||||
|                     value: formValues[name], | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         throw new CoreError('User profile field handler not found.'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the data to send for a list of fields based on the input data. | ||||
|      * | ||||
|      * @param fields User fields to get the data for. | ||||
|      * @param signup True if user is in signup page. | ||||
|      * @param registerAuth Register auth method. E.g. 'email'. | ||||
|      * @param formValues Form values. | ||||
|      * @return Data to send. | ||||
|      */ | ||||
|     async getDataForFields( | ||||
|         fields: (AuthEmailSignupProfileField | CoreUserProfileField)[] | undefined, | ||||
|         signup: boolean = false, | ||||
|         registerAuth: string = '', | ||||
|         formValues: Record<string, unknown>, | ||||
|     ): Promise<CoreUserProfileFieldHandlerData[]> { | ||||
|         if (!fields) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         const result: CoreUserProfileFieldHandlerData[] = []; | ||||
| 
 | ||||
|         await Promise.all(fields.map(async (field) => { | ||||
|             try { | ||||
|                 const data = await this.getDataForField(field, signup, registerAuth, formValues); | ||||
| 
 | ||||
|                 if (data) { | ||||
|                     result.push(data); | ||||
|                 } | ||||
|             } catch (error) { | ||||
|                 // Ignore errors.
 | ||||
|             } | ||||
|         })); | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if any of the profile fields is not supported in the app. | ||||
|      * | ||||
|      * @param fields List of fields. | ||||
|      * @return Whether any of the profile fields is not supported in the app. | ||||
|      */ | ||||
|     hasRequiredUnsupportedField(fields?: AuthEmailSignupProfileField[]): boolean { | ||||
|         if (!fields || !fields.length) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return fields.some((field) => field.required && !this.hasHandler(this.getType(field))); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -18,6 +18,7 @@ import { Routes } from '@angular/router'; | ||||
| import { CoreMainMenuMoreRoutingModule } from '@features/mainmenu/pages/more/more-routing.module'; | ||||
| import { CORE_SITE_SCHEMAS } from '@services/sites'; | ||||
| import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/db/user'; | ||||
| import { CoreUserComponentsModule } from './components/components.module'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
| @ -29,6 +30,7 @@ const routes: Routes = [ | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         CoreMainMenuMoreRoutingModule.forChild({ siblings: routes }), | ||||
|         CoreUserComponentsModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|         { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user