diff --git a/src/core/components/components.module.ts b/src/core/components/components.module.ts
index 76c6ec45a..416cd3999 100644
--- a/src/core/components/components.module.ts
+++ b/src/core/components/components.module.ts
@@ -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 {}
diff --git a/src/core/components/dynamic-component/core-dynamic-component.html b/src/core/components/dynamic-component/core-dynamic-component.html
new file mode 100644
index 000000000..99c89fec9
--- /dev/null
+++ b/src/core/components/dynamic-component/core-dynamic-component.html
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/core/components/dynamic-component/dynamic-component.ts b/src/core/components/dynamic-component/dynamic-component.ts
new file mode 100644
index 000000000..5ae5512ab
--- /dev/null
+++ b/src/core/components/dynamic-component/dynamic-component.ts
@@ -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:
+ *
+ *
+ * Cannot render the data.
+ *
+ *
+ * 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;
+ @Input() data?: Record;
+
+ // 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; // To detect changes in the data input.
+ protected lastComponent?: Type;
+
+ 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(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];
+ }
+ }
+
+}
diff --git a/src/core/features/login/pages/email-signup/email-signup.html b/src/core/features/login/pages/email-signup/email-signup.html
index bbef473a6..95378a395 100644
--- a/src/core/features/login/pages/email-signup/email-signup.html
+++ b/src/core/features/login/pages/email-signup/email-signup.html
@@ -172,8 +172,8 @@
{{ category.name }}
-
+
diff --git a/src/core/features/login/pages/email-signup/email-signup.module.ts b/src/core/features/login/pages/email-signup/email-signup.module.ts
index 7956c875d..b1a454123 100644
--- a/src/core/features/login/pages/email-signup/email-signup.module.ts
+++ b/src/core/features/login/pages/email-signup/email-signup.module.ts
@@ -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,
diff --git a/src/core/features/login/pages/email-signup/email-signup.ts b/src/core/features/login/pages/email-signup/email-signup.ts
index d5e080862..8935a09e3 100644
--- a/src/core/features/login/pages/email-signup/email-signup.ts
+++ b/src/core/features/login/pages/email-signup/email-signup.ts
@@ -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(
+ CoreWS.instance.callAjax(
'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 = {
+ 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(
+ // 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(
'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('core_auth_is_minor', params, { siteUrl: this.siteUrl });
+ const result = await CoreWS.instance.callAjax('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.
};
diff --git a/src/core/features/user/components/components.module.ts b/src/core/features/user/components/components.module.ts
new file mode 100644
index 000000000..cd7b1d33f
--- /dev/null
+++ b/src/core/features/user/components/components.module.ts
@@ -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 {}
diff --git a/src/core/features/user/components/user-profile-field/core-user-profile-field.html b/src/core/features/user/components/user-profile-field/core-user-profile-field.html
new file mode 100644
index 000000000..1f3a37007
--- /dev/null
+++ b/src/core/features/user/components/user-profile-field/core-user-profile-field.html
@@ -0,0 +1 @@
+
diff --git a/src/core/features/user/components/user-profile-field/user-profile-field.ts b/src/core/features/user/components/user-profile-field/user-profile-field.ts
new file mode 100644
index 000000000..6a4aae6c5
--- /dev/null
+++ b/src/core/features/user/components/user-profile-field/user-profile-field.ts
@@ -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; // 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 {
+ 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;
+};
diff --git a/src/core/features/user/pages/about/about.html b/src/core/features/user/pages/about/about.html
index c45d302d3..633ecd788 100644
--- a/src/core/features/user/pages/about/about.html
+++ b/src/core/features/user/pages/about/about.html
@@ -41,7 +41,7 @@
{{ 'core.user.address' | translate}}
-
+
{{ user.address }}
diff --git a/src/core/features/user/pages/about/about.module.ts b/src/core/features/user/pages/about/about.module.ts
index daa3c8811..2c2e2c340 100644
--- a/src/core/features/user/pages/about/about.module.ts
+++ b/src/core/features/user/pages/about/about.module.ts
@@ -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,
diff --git a/src/core/features/user/services/user-profile-field-delegate.ts b/src/core/features/user/services/user-profile-field-delegate.ts
new file mode 100644
index 000000000..6ec0e6e3d
--- /dev/null
+++ b/src/core/features/user/services/user-profile-field-delegate.ts
@@ -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 | Promise>;
+
+ /**
+ * 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,
+ ): Promise;
+}
+
+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 {
+
+ 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 | 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,
+ ): Promise {
+ 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,
+ ): Promise {
+ 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)));
+ }
+
+}
diff --git a/src/core/features/user/user.module.ts b/src/core/features/user/user.module.ts
index 26f0e6c98..6c580f42d 100644
--- a/src/core/features/user/user.module.ts
+++ b/src/core/features/user/user.module.ts
@@ -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: [
{