From c2946cc797c26ed3c7adaba6f9fccb022fa4e4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 12 Jan 2018 13:57:53 +0100 Subject: [PATCH 01/13] MOBILE-2317 user: Implement user-link and user module structure --- src/app/app.module.ts | 2 + src/core/mainmenu/pages/more/more.html | 2 +- src/core/user/lang/en.json | 3 ++ src/core/user/pages/profile/profile.html | 7 +++ src/core/user/pages/profile/profile.module.ts | 29 ++++++++++++ src/core/user/pages/profile/profile.scss | 3 ++ src/core/user/pages/profile/profile.ts | 28 +++++++++++ src/core/user/providers/delegate.ts | 30 ++++++++++++ src/core/user/providers/user.ts | 24 ++++++++++ src/core/user/user.module.ts | 29 ++++++++++++ src/directives/directives.module.ts | 7 ++- src/directives/user-link.ts | 47 +++++++++++++++++++ 12 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 src/core/user/lang/en.json create mode 100644 src/core/user/pages/profile/profile.html create mode 100644 src/core/user/pages/profile/profile.module.ts create mode 100644 src/core/user/pages/profile/profile.scss create mode 100644 src/core/user/pages/profile/profile.ts create mode 100644 src/core/user/providers/delegate.ts create mode 100644 src/core/user/providers/user.ts create mode 100644 src/core/user/user.module.ts create mode 100644 src/directives/user-link.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ffe8b4705..2a824241a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -60,6 +60,7 @@ import { CoreSharedFilesModule } from '../core/sharedfiles/sharedfiles.module'; import { CoreCourseModule } from '../core/course/course.module'; import { CoreSiteHomeModule } from '../core/sitehome/sitehome.module'; import { CoreContentLinksModule } from '../core/contentlinks/contentlinks.module'; +import { CoreUserModule } from '../core/user/user.module'; // Addon modules. import { AddonCalendarModule } from '../addon/calendar/calendar.module'; @@ -97,6 +98,7 @@ export function createTranslateLoader(http: HttpClient) { CoreCourseModule, CoreSiteHomeModule, CoreContentLinksModule, + CoreUserModule, AddonCalendarModule ], bootstrap: [IonicApp], diff --git a/src/core/mainmenu/pages/more/more.html b/src/core/mainmenu/pages/more/more.html index e2044e6b9..089e4ad3c 100644 --- a/src/core/mainmenu/pages/more/more.html +++ b/src/core/mainmenu/pages/more/more.html @@ -5,7 +5,7 @@ - + {{ 'core.pictureof' | translate:{$a: siteInfo.fullname} }} diff --git a/src/core/user/lang/en.json b/src/core/user/lang/en.json new file mode 100644 index 000000000..0e0dcd235 --- /dev/null +++ b/src/core/user/lang/en.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/src/core/user/pages/profile/profile.html b/src/core/user/pages/profile/profile.html new file mode 100644 index 000000000..d3bf591b3 --- /dev/null +++ b/src/core/user/pages/profile/profile.html @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/core/user/pages/profile/profile.module.ts b/src/core/user/pages/profile/profile.module.ts new file mode 100644 index 000000000..0e0780fee --- /dev/null +++ b/src/core/user/pages/profile/profile.module.ts @@ -0,0 +1,29 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { IonicPageModule } from 'ionic-angular'; +import { CoreUserProfilePage } from './profile'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; + +@NgModule({ + declarations: [ + CoreUserProfilePage, + ], + imports: [ + CoreDirectivesModule, + IonicPageModule.forChild(CoreUserProfilePage), + ], +}) +export class CoreUserProfilePageModule {} diff --git a/src/core/user/pages/profile/profile.scss b/src/core/user/pages/profile/profile.scss new file mode 100644 index 000000000..76f53dc77 --- /dev/null +++ b/src/core/user/pages/profile/profile.scss @@ -0,0 +1,3 @@ +page-core-user-profile { + +} \ No newline at end of file diff --git a/src/core/user/pages/profile/profile.ts b/src/core/user/pages/profile/profile.ts new file mode 100644 index 000000000..983092276 --- /dev/null +++ b/src/core/user/pages/profile/profile.ts @@ -0,0 +1,28 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { IonicPage } from 'ionic-angular'; +import { CoreUserProvider } from '../../providers/user'; + +/** + * Page that displays an user profile page. + */ +@IonicPage({segment: "core-user-profile"}) +@Component({ + selector: 'page-core-user-profile', + templateUrl: 'profile.html', +}) +export class CoreUserProfilePage { +} \ No newline at end of file diff --git a/src/core/user/providers/delegate.ts b/src/core/user/providers/delegate.ts new file mode 100644 index 000000000..b2ea852eb --- /dev/null +++ b/src/core/user/providers/delegate.ts @@ -0,0 +1,30 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { CoreLoggerProvider } from '../../../providers/logger'; + +/** + * Service to interact with plugins to be shown in user profile. Provides functions to register a plugin + * and notify an update in the data. + */ +@Injectable() +export class CoreUserDelegate { + protected logger; + + constructor(logger: CoreLoggerProvider) { + this.logger = logger.getInstance('CoreUserDelegate'); + } + +} diff --git a/src/core/user/providers/user.ts b/src/core/user/providers/user.ts new file mode 100644 index 000000000..772ade7eb --- /dev/null +++ b/src/core/user/providers/user.ts @@ -0,0 +1,24 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; + +/** + * Service to provide user functionalities. + */ +@Injectable() +export class CoreUserProvider { + + constructor() {} +} diff --git a/src/core/user/user.module.ts b/src/core/user/user.module.ts new file mode 100644 index 000000000..18296d3ef --- /dev/null +++ b/src/core/user/user.module.ts @@ -0,0 +1,29 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { CoreUserDelegate } from './providers/delegate'; +import { CoreUserProvider } from './providers/user'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + CoreUserDelegate, + CoreUserProvider + ] +}) +export class CoreUserModule {} diff --git a/src/directives/directives.module.ts b/src/directives/directives.module.ts index 105f6565b..11d708992 100644 --- a/src/directives/directives.module.ts +++ b/src/directives/directives.module.ts @@ -18,6 +18,7 @@ import { CoreExternalContentDirective } from './external-content'; import { CoreFormatTextDirective } from './format-text'; import { CoreLinkDirective } from './link'; import { CoreKeepKeyboardDirective } from './keep-keyboard'; +import { CoreUserLinkDirective } from './user-link'; @NgModule({ declarations: [ @@ -25,7 +26,8 @@ import { CoreKeepKeyboardDirective } from './keep-keyboard'; CoreExternalContentDirective, CoreFormatTextDirective, CoreKeepKeyboardDirective, - CoreLinkDirective + CoreLinkDirective, + CoreUserLinkDirective ], imports: [], exports: [ @@ -33,7 +35,8 @@ import { CoreKeepKeyboardDirective } from './keep-keyboard'; CoreExternalContentDirective, CoreFormatTextDirective, CoreKeepKeyboardDirective, - CoreLinkDirective + CoreLinkDirective, + CoreUserLinkDirective ] }) export class CoreDirectivesModule {} diff --git a/src/directives/user-link.ts b/src/directives/user-link.ts new file mode 100644 index 000000000..0dcd85862 --- /dev/null +++ b/src/directives/user-link.ts @@ -0,0 +1,47 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Directive, Input, OnInit, ElementRef } from '@angular/core'; +import { NavController } from 'ionic-angular'; +/** + * Directive to open a link in external browser. + */ +@Directive({ + selector: '[core-user-link]' +}) +export class CoreUserLinkDirective implements OnInit { + @Input() userId: number; // User id to open the profile. + @Input() courseId?: number; // If set, course id to show the user info related to that course. + + protected element: HTMLElement; + + constructor(element: ElementRef, private navCtrl: NavController) { + // This directive can be added dynamically. In that case, the first param is the anchor HTMLElement. + this.element = element.nativeElement || element; + } + + /** + * Function executed when the component is initialized. + */ + ngOnInit() { + this.element.addEventListener('click', (event) => { + // If the event prevented default action, do nothing. + if (!event.defaultPrevented) { + event.preventDefault(); + event.stopPropagation(); + this.navCtrl.push('CoreUserProfilePage', {userId: this.userId, courseId: this.courseId}); + } + }); + } +} \ No newline at end of file From 2b19b5172d52af7fab572ca611acb77ebbbafa14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 12 Jan 2018 16:24:24 +0100 Subject: [PATCH 02/13] MOBILE-2317 user: Implement user profile page --- src/app/app.scss | 3 +- src/assets/img/user-avatar.png | Bin 0 -> 1233 bytes src/core/fileuploader/providers/delegate.ts | 4 +- src/core/user/lang/en.json | 10 +- src/core/user/pages/profile/profile.html | 31 +++ src/core/user/pages/profile/profile.module.ts | 4 + src/core/user/pages/profile/profile.scss | 7 +- src/core/user/pages/profile/profile.ts | 132 ++++++++++- src/core/user/providers/helper.ts | 70 ++++++ src/core/user/providers/user.ts | 211 +++++++++++++++++- src/core/user/user.module.ts | 4 +- 11 files changed, 468 insertions(+), 8 deletions(-) create mode 100644 src/assets/img/user-avatar.png create mode 100644 src/core/user/providers/helper.ts diff --git a/src/app/app.scss b/src/app/app.scss index 862c6232a..79b3808c7 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -115,7 +115,8 @@ } > img:first-child, - ion-avatar img { + ion-avatar img, + img { display: block; margin: auto; width: 90px; diff --git a/src/assets/img/user-avatar.png b/src/assets/img/user-avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..0345e26c8279b7917ad8f76c2854c965a2fd680f GIT binary patch literal 1233 zcmV;?1TOoDP)CJP_~QSd zef>L6egs_T8rMhf+j;{(EhfKt^8b1AODq4Cky#|s_UeEC3$!A?05AZQCwa!z;nI%| zkeA!<m=H2rJB#rXF85ainPvdfr-Eeu!t1Kyp?!?y#7Xs~At@_3mXXm=ArD9nCif&h2lPc%t0MujJ9Xci9&8HWOER9F+#|brJB*3GpqrV7iEzP2_`q_u^?47IfK4T%e3V-o;_<-S zcLFI)J>3mOVEG z@+3(FmgvpB92dHH3gfWQChnk!#%eBl-w`PHV#7+mu2nd9;;HsGnX@-B?! zx2sdAON_|i!$Az@nDazgJRz)5@6tQd@-`0FL+==uZs&|92A1F5wyHBC!%^7s$1+b) z2c#Z{1Zx25sB7OWSTxk5r+9aM&W6L)Ic;FvcWE>+2KgdF3E zWW>f617{eK(N`PoIB_S*J$nsQXh__(bpuuYz&P$gxY&3^Z-QL^_;UX_h0z8i$%qOI z#!x-vgJNCG7Q5riI)?~P+Cs#5Is^u&uZsN{&sk}cfacCF(_OM4T(a{%1Y!nUy7P`7 z3*~z#KxX80 z;IViO-(bvzOOs21Q+a~0e}989+lZ>Iw(HlJC#n!EVrJ^pfS}tOOMVTRu@Gw*5yg-v z2XL7h#0t%<_oK5*cHBO7bwW(k8v^uAVdV8XMrw{ewPG?+EX@;P^3g{tYlXy?J;&i< z=Q*fM4V;-Dt_Q`fO`!pL4B_faqU_m*-J{{+2INLB^NPE&UJI_e^ocuF(q73(vjLB| vQzh-NhKm{KO7|b!B`KKJ$rJUyu6N@ + + + + + + +
+ {{ 'core.pictureof' | translate:{$a: user.fullname} }} + {{ 'core.pictureof' | translate:{$a: user.fullname} }} + +
+

+

+

+ {{ 'core.user.roles' | translate}}{{'core.labelsep' | translate}} + +

+
+ +
+ + + +
+ + + +
\ No newline at end of file diff --git a/src/core/user/pages/profile/profile.module.ts b/src/core/user/pages/profile/profile.module.ts index 0e0780fee..f8a6d8922 100644 --- a/src/core/user/pages/profile/profile.module.ts +++ b/src/core/user/pages/profile/profile.module.ts @@ -14,8 +14,10 @@ import { NgModule } from '@angular/core'; import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; import { CoreUserProfilePage } from './profile'; import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { CoreComponentsModule } from '../../../../components/components.module'; @NgModule({ declarations: [ @@ -23,7 +25,9 @@ import { CoreDirectivesModule } from '../../../../directives/directives.module'; ], imports: [ CoreDirectivesModule, + CoreComponentsModule, IonicPageModule.forChild(CoreUserProfilePage), + TranslateModule.forChild() ], }) export class CoreUserProfilePageModule {} diff --git a/src/core/user/pages/profile/profile.scss b/src/core/user/pages/profile/profile.scss index 76f53dc77..a5844f405 100644 --- a/src/core/user/pages/profile/profile.scss +++ b/src/core/user/pages/profile/profile.scss @@ -1,3 +1,8 @@ page-core-user-profile { - + .core-icon-foreground { + position: relative; + left: 60px; + bottom: 30px; + font-size: 24px; + } } \ No newline at end of file diff --git a/src/core/user/pages/profile/profile.ts b/src/core/user/pages/profile/profile.ts index 983092276..3d7bd28c7 100644 --- a/src/core/user/pages/profile/profile.ts +++ b/src/core/user/pages/profile/profile.ts @@ -13,8 +13,16 @@ // limitations under the License. import { Component } from '@angular/core'; -import { IonicPage } from 'ionic-angular'; +import { IonicPage, NavParams } from 'ionic-angular'; import { CoreUserProvider } from '../../providers/user'; +import { CoreUserHelperProvider } from '../../providers/helper'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreCoursesProvider } from '../../../courses/providers/courses'; +import { CoreEventsProvider } from '../../../../providers/events'; +import { CoreSitesProvider } from '../../../../providers/sites'; +import { CoreMimetypeUtilsProvider } from '../../../../providers/utils/mimetype'; +import { CoreFileUploaderHelperProvider } from '../../../fileuploader/providers/helper'; /** * Page that displays an user profile page. @@ -25,4 +33,126 @@ import { CoreUserProvider } from '../../providers/user'; templateUrl: 'profile.html', }) export class CoreUserProfilePage { + protected courseId: number; + protected userId: number; + protected site; + protected obsProfileRefreshed: any; + + userLoaded: boolean = false; + isLoadingHandlers: boolean = false; + user: any = {}; + title: string; + isDeleted: boolean = false; + canChangeProfilePicture: boolean = false; + + constructor(private navParams: NavParams, private userProvider: CoreUserProvider, private userHelper: CoreUserHelperProvider, + private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private eventsProvider: CoreEventsProvider, + private coursesProvider: CoreCoursesProvider, private sitesProvider: CoreSitesProvider, + private mimetypeUtils: CoreMimetypeUtilsProvider, private fileUploaderHelper: CoreFileUploaderHelperProvider) { + this.userId = navParams.get('userId'); + this.courseId = navParams.get('courseId'); + + this.site = this.sitesProvider.getCurrentSite(); + + // Allow to change the profile image only in the app profile page. + this.canChangeProfilePicture = + (!this.courseId || this.courseId == this.site.getSiteHomeId()) && + this.userId == this.site.getUserId() && + this.site.canUploadFiles() && + this.site.wsAvailable('core_user_update_picture') && + !this.userProvider.isUpdatePictureDisabledInSite(this.site); + + this.obsProfileRefreshed = eventsProvider.on(CoreUserProvider.PROFILE_REFRESHED, (data) => { + if (typeof data.user != "undefined") { + this.user.email = data.user.email; + this.user.address = this.userHelper.formatAddress("", data.user.city, data.user.country); + } + }, sitesProvider.getCurrentSiteId()); + } + + /** + * View loaded. + */ + ionViewDidLoad() { + this.fetchUser().then(() => { + return this.userProvider.logView(this.userId, this.courseId).catch((error) => { + this.isDeleted = error === this.translate.instant('core.userdeleted'); + }); + }).finally(() => { + this.userLoaded = true; + }); + } + + /** + * Fetches the user and updates the view. + */ + fetchUser() : Promise { + return this.userProvider.getProfile(this.userId, this.courseId).then((user) => { + + user.address = this.userHelper.formatAddress("", user.city, user.country); + user.roles = this.userHelper.formatRoleList(user.roles); + + this.user = user; + this.title = user.fullname; + + this.isLoadingHandlers = true; + + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.user.errorloaduser', true); + }); + } + + + /** + * Opens dialog to change profile picture. + */ + changeProfilePicture(){ + let maxSize = -1, + title = this.translate.instant('core.user.newpicture'), + mimetypes = this.mimetypeUtils.getGroupMimeInfo('image', 'mimetypes'); + + return this.fileUploaderHelper.selectAndUploadFile(maxSize, title, mimetypes).then((result) => { + let modal = this.domUtils.showModalLoading('core.sending', true); + + return this.userProvider.changeProfilePicture(result.itemid, this.userId).then((profileImageURL) => { + this.eventsProvider.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, {userId: this.userId, picture: profileImageURL}); + this.sitesProvider.updateSiteInfo(this.site.getId()); + this.refreshUser(); + }).finally(function() { + modal.dismiss(); + }); + }).catch((message) => { + if (message) { + this.domUtils.showErrorModal(message); + } + }); + } + + /** + * Refresh the user. + * + * @param {any} refresher Refresher. + */ + refreshUser(refresher?: any) { + let promises = []; + + promises.push(this.userProvider.invalidateUserCache(this.userId)); + promises.push(this.coursesProvider.invalidateUserNavigationOptions()); + promises.push(this.coursesProvider.invalidateUserAdministrationOptions()); + + Promise.all(promises).finally(() => { + this.fetchUser().finally(() => { + this.eventsProvider.trigger(CoreUserProvider.PROFILE_REFRESHED, {courseId: this.courseId, userId: this.userId, + user: this.user}, this.site.getId()); + refresher && refresher.complete(); + }); + }); + } + + /** + * Page destroyed. + */ + ngOnDestroy() { + this.obsProfileRefreshed && this.obsProfileRefreshed.off(); + } } \ No newline at end of file diff --git a/src/core/user/providers/helper.ts b/src/core/user/providers/helper.ts new file mode 100644 index 000000000..e09c223d5 --- /dev/null +++ b/src/core/user/providers/helper.ts @@ -0,0 +1,70 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { CoreLoggerProvider } from '../../../providers/logger'; +import { TranslateService } from '@ngx-translate/core'; + +/** + * Service that provides some features regarding users information. + */ +@Injectable() +export class CoreUserHelperProvider { + protected logger; + + constructor(logger: CoreLoggerProvider, private translate: TranslateService) { + this.logger = logger.getInstance('CoreUserHelperProvider'); + } + + /** + * Formats a user address, concatenating address, city and country. + * + * @param {string} address Address. + * @param {string} city City. + * @param {string} country Country. + * @return {string} Formatted address. + */ + formatAddress(address: string, city: string, country: string) : string { + let separator = this.translate.instant('core.listsep'), + values = [address, city, country]; + + values = values.filter((value) => { + return value && value.length > 0; + }); + + return values.join(separator + " "); + } + + /** + * Formats a user role list, translating and concatenating them. + * + * @param {any[]} [roles] List of user roles. + * @return {string} The formatted roles. + */ + formatRoleList(roles?: any[]) : string { + if (!roles || roles.length <= 0) { + return ""; + } + + let separator = this.translate.instant('core.listsep'); + + roles.map((value) => { + console.error(value); + let translation = this.translate.instant('core.user.' + value.shortname); + return translation.indexOf('core.user.') < 0 ? translation : value.shortname; + }); + + return roles.join(separator + " "); + } +} diff --git a/src/core/user/providers/user.ts b/src/core/user/providers/user.ts index 772ade7eb..b243db4c9 100644 --- a/src/core/user/providers/user.ts +++ b/src/core/user/providers/user.ts @@ -13,12 +13,221 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { CoreLoggerProvider } from '../../../providers/logger'; +import { CoreSite } from '../../../classes/site'; +import { CoreSitesProvider } from '../../../providers/sites'; +import { CoreUtilsProvider } from '../../../providers/utils/utils'; /** * Service to provide user functionalities. */ @Injectable() export class CoreUserProvider { + public static PROFILE_REFRESHED = 'CoreUserProfileRefreshed'; + public static PROFILE_PICTURE_UPDATED = 'CoreUserProfilePictureUpdated'; + protected ROOT_CACHE_KEY = 'mmUser:'; - constructor() {} + // Variables for database. + protected USERS_TABLE = 'users'; + protected tablesSchema = [ + { + name: this.USERS_TABLE, + columns: [ + { + name: 'id', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'fullname', + type: 'TEXT' + }, + { + name: 'profileimageurl', + type: 'TEXT' + } + ] + } + ]; + + protected logger; + + constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider) { + this.logger = logger.getInstance('CoreUserProvider'); + this.sitesProvider.createTablesFromSchema(this.tablesSchema); + } + + /** + * Change the given user profile picture. + * + * @param {number} draftItemId New picture draft item id. + * @param {number} id User ID. + * @return {Promise} Promise resolve with the new profileimageurl + */ + changeProfilePicture(draftItemId: number, userId: number): Promise { + var data = { + 'draftitemid': draftItemId, + 'delete': 0, + 'userid': userId + }; + + return this.sitesProvider.getCurrentSite().write('core_user_update_picture', data).then((result) => { + if (!result.success) { + return Promise.reject(null); + } + return result.profileimageurl; + }); + } + + /** + * Get user profile. The type of profile retrieved depends on the params. + * + * @param {number} userId User's ID. + * @param {number} [courseId] Course ID to get course profile, undefined or 0 to get site profile. + * @param {boolean} [forceLocal] True to retrieve the user data from local DB, false to retrieve it from WS. + * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. + * @return {Promise} Promise resolved with the user data. + */ + getProfile(userId: number, courseId: number, forceLocal = false, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + if (forceLocal) { + return this.getUserFromLocalDb(userId, siteId).catch(function() { + return this.getUserFromWS(userId, courseId, siteId); + }); + } + return this.getUserFromWS(userId, courseId, siteId).catch(function() { + return this.getUserFromLocalDb(userId, siteId); + }); + } + + /** + * Invalidates user WS calls. + * + * @param {number} userId User ID. + * @return {string} Cache key. + */ + protected getUserCacheKey(userId): string { + return this.ROOT_CACHE_KEY + 'data:' + userId; + } + + /** + * Get user basic information from local DB. + * + * @param {number} userId User ID. + * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. + * @return {Promise} Promise resolve when the user is retrieved. + */ + protected getUserFromLocalDb(userId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.getDb().getRecord(this.USERS_TABLE, {id: userId}); + }); + } + + /** + * Get user profile from WS. + * + * @param {number} userId User ID. + * @param {number} [courseId] Course ID to get course profile, undefined or 0 to get site profile. + * @param {string} [siteId] ID of the site. If not defined, use current site. + * @return {Promise} Promise resolve when the user is retrieved. + */ + protected getUserFromWS(userId: number, courseId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + let presets = { + cacheKey: this.getUserCacheKey(userId) + }, + wsName, data; + + // Determine WS and data to use. + if (courseId && courseId != site.getSiteHomeId()) { + this.logger.debug(`Get participant with ID '${userId}' in course '${courseId}`); + wsName = 'core_user_get_course_user_profiles'; + data = { + "userlist[0][userid]": userId, + "userlist[0][courseid]": courseId + }; + } else { + this.logger.debug(`Get user with ID '${userId}'`); + wsName = 'core_user_get_users_by_field'; + data = { + 'field': 'id', + 'values[0]': userId + }; + } + + return site.read(wsName, data, presets).then((users) => { + if (users.length == 0) { + return Promise.reject('Cannot retrieve user info.'); + } + + var user = users.shift(); + if (user.country) { + user.country = this.utils.getCountryName(user.country); + } + this.storeUser(user.id, user.fullname, user.profileimageurl); + return user; + }); + + }); + } + + /** + * Invalidates user WS calls. + * + * @param {number} userId User ID. + * @param {string} [siteId] Site Id. If not defined, use current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateUserCache(userId: number, siteId?: string) : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getUserCacheKey(userId)); + }); + }; + + /** + * Check if update profile picture is disabled in a certain site. + * + * @param {CoreSite} [site] Site. If not defined, use current site. + * @return {boolean} True if disabled, false otherwise. + */ + isUpdatePictureDisabledInSite(site?: CoreSite) : boolean { + site = site || this.sitesProvider.getCurrentSite(); + return site.isFeatureDisabled('$mmUserDelegate_picture'); + }; + + + /** + * Log User Profile View in Moodle. + * @param {number} userId User ID. + * @param {number} courseId Course ID. + * @return {Promise} Promise resolved when done. + */ + logView(userId: number, courseId?: number) : Promise { + return this.sitesProvider.getCurrentSite().write('core_user_view_user_profile', { + userid: userId, + courseid: courseId + }); + } + + /** + * Store user basic information in local DB to be retrieved if the WS call fails. + * + * @param {number} userId User ID. + * @param {string} fullname User full name. + * @param {string} avatar User avatar URL. + * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. + * @return {Promise} Promise resolve when the user is stored. + */ + protected storeUser(userId: number, fullname: string, avatar: string, siteId?: string) { + return this.sitesProvider.getSite(siteId).then((site) => { + let userRecord = { + id: userId, + fullname: fullname, + profileimageurl: avatar + }; + + return site.getDb().insertOrUpdateRecord(this.USERS_TABLE, userRecord, {id: userId}); + }); + } } diff --git a/src/core/user/user.module.ts b/src/core/user/user.module.ts index 18296d3ef..2aeae9a9e 100644 --- a/src/core/user/user.module.ts +++ b/src/core/user/user.module.ts @@ -15,6 +15,7 @@ import { NgModule } from '@angular/core'; import { CoreUserDelegate } from './providers/delegate'; import { CoreUserProvider } from './providers/user'; +import { CoreUserHelperProvider } from './providers/helper'; @NgModule({ declarations: [ @@ -23,7 +24,8 @@ import { CoreUserProvider } from './providers/user'; ], providers: [ CoreUserDelegate, - CoreUserProvider + CoreUserProvider, + CoreUserHelperProvider ] }) export class CoreUserModule {} From 2f2b16f9fab1d167443e36be36c999bc58b879d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 15 Jan 2018 13:38:35 +0100 Subject: [PATCH 03/13] MOBILE-2317 user: Implement about page --- src/core/user/lang/en.json | 12 ++- src/core/user/pages/about/about.html | 71 +++++++++++++++ src/core/user/pages/about/about.module.ts | 33 +++++++ src/core/user/pages/about/about.scss | 2 + src/core/user/pages/about/about.ts | 100 ++++++++++++++++++++++ src/core/user/pages/profile/profile.html | 2 +- 6 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 src/core/user/pages/about/about.html create mode 100644 src/core/user/pages/about/about.module.ts create mode 100644 src/core/user/pages/about/about.scss create mode 100644 src/core/user/pages/about/about.ts diff --git a/src/core/user/lang/en.json b/src/core/user/lang/en.json index d04f99898..861570f59 100644 --- a/src/core/user/lang/en.json +++ b/src/core/user/lang/en.json @@ -1,11 +1,21 @@ { + "address": "Address", + "city": "City/town", + "contact": "Contact", + "country": "Country", + "description": "Description", "details": "Details", "detailsnotavailable": "The details of this user are not available to you.", "editingteacher": "Teacher", + "email": "Email address", "errorloaduser": "Error loading user.", + "interests": "Interests", "manager": "Manager", "newpicture": "New picture", + "phone1": "Phone", + "phone2": "Mobile phone", "roles": "Roles", "student": "Student", - "teacher": "Non-editing teacher" + "teacher": "Non-editing teacher", + "webpage": "Web page" } \ No newline at end of file diff --git a/src/core/user/pages/about/about.html b/src/core/user/pages/about/about.html new file mode 100644 index 000000000..6556144a4 --- /dev/null +++ b/src/core/user/pages/about/about.html @@ -0,0 +1,71 @@ + + + + + + + + + + +
+ + {{ 'core.user.contact' | translate}} + +

{{ 'core.user.email' | translate }}

+

+ +

+
+ +

{{ 'core.user.phone1' | translate}}

+

+ +

+
+ +

{{ 'core.user.phone2' | translate}}

+

+ +

+
+ +

{{ 'core.user.address' | translate}}

+

+ + + + +

+
+ +

{{ 'core.user.city' | translate}}

+

+
+ +

{{ 'core.user.country' | translate}}

+

+
+
+ + {{ 'core.userdetails' | translate}} + +

{{ 'core.user.webpage' | translate}}

+

+ +

+
+ +

{{ 'core.user.interests' | translate}}

+

+
+
+ + {{ 'core.user.description' | translate}} + +

+
+
+
+ +
\ No newline at end of file diff --git a/src/core/user/pages/about/about.module.ts b/src/core/user/pages/about/about.module.ts new file mode 100644 index 000000000..07f43dbba --- /dev/null +++ b/src/core/user/pages/about/about.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreUserAboutPage } from './about'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { CoreComponentsModule } from '../../../../components/components.module'; + +@NgModule({ + declarations: [ + CoreUserAboutPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + IonicPageModule.forChild(CoreUserAboutPage), + TranslateModule.forChild() + ], +}) +export class CoreUserAboutPageModule {} diff --git a/src/core/user/pages/about/about.scss b/src/core/user/pages/about/about.scss new file mode 100644 index 000000000..1752c93e5 --- /dev/null +++ b/src/core/user/pages/about/about.scss @@ -0,0 +1,2 @@ +page-core-user-about { +} \ No newline at end of file diff --git a/src/core/user/pages/about/about.ts b/src/core/user/pages/about/about.ts new file mode 100644 index 000000000..603e8e43b --- /dev/null +++ b/src/core/user/pages/about/about.ts @@ -0,0 +1,100 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { IonicPage, NavParams, Platform } from 'ionic-angular'; +import { CoreUserProvider } from '../../providers/user'; +import { CoreUserHelperProvider } from '../../providers/helper'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { CoreCoursesProvider } from '../../../courses/providers/courses'; +import { CoreEventsProvider } from '../../../../providers/events'; +import { CoreSitesProvider } from '../../../../providers/sites'; + +/** + * Page that displays an user about page. + */ +@IonicPage({segment: "core-user-about"}) +@Component({ + selector: 'page-core-user-about', + templateUrl: 'about.html', +}) +export class CoreUserAboutPage { + protected courseId: number; + protected userId: number; + protected siteId; + + userLoaded: boolean = false; + hasContact: boolean = false; + hasDetails: boolean = false; + isAndroid: boolean = false; + user: any = {}; + title: string; + + constructor(private navParams: NavParams, private userProvider: CoreUserProvider, private userHelper: CoreUserHelperProvider, + private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider, + private sitesProvider: CoreSitesProvider, private platform: Platform) { + + this.userId = navParams.get('userId'); + this.courseId = navParams.get('courseId'); + this.isAndroid = this.platform.is('android'); + + this.siteId = this.sitesProvider.getCurrentSite().getId(); + } + + /** + * View loaded. + */ + ionViewDidLoad() { + this.fetchUser().finally(() => { + this.userLoaded = true; + }); + } + + /** + * Fetches the user and updates the view. + */ + fetchUser() : Promise { + return this.userProvider.getProfile(this.userId, this.courseId).then((user) => { + + if (user.address) { + user.address = this.userHelper.formatAddress(user.address, user.city, user.country); + user.encodedAddress = encodeURIComponent(user.address); + } + + this.hasContact = user.email || user.phone1 || user.phone2 || user.city || user.country || user.address; + this.hasDetails = user.url || user.interests || (user.customfields && user.customfields.length > 0); + + this.user = user; + this.title = user.fullname; + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.user.errorloaduser', true); + }); + } + + /** + * Refresh the user. + * + * @param {any} refresher Refresher. + */ + refreshUser(refresher?: any) { + this.userProvider.invalidateUserCache(this.userId).finally(() => { + this.fetchUser().finally(() => { + this.eventsProvider.trigger(CoreUserProvider.PROFILE_REFRESHED, {courseId: this.courseId, userId: this.userId, + user: this.user}, this.siteId); + refresher && refresher.complete(); + }); + }); + } + +} \ No newline at end of file diff --git a/src/core/user/pages/profile/profile.html b/src/core/user/pages/profile/profile.html index 2aec9846a..d730e1182 100644 --- a/src/core/user/pages/profile/profile.html +++ b/src/core/user/pages/profile/profile.html @@ -23,7 +23,7 @@

- From e836171c029c4a96807b2b554f004f1146431770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 15 Jan 2018 15:57:42 +0100 Subject: [PATCH 04/13] MOBILE-2317 user: Add User Delegate --- src/addon/calendar/calendar.module.ts | 2 +- .../{handlers.ts => mainmenu-handler.ts} | 0 src/classes/delegate.ts | 238 ++++++++++++++++++ src/core/mainmenu/providers/delegate.ts | 163 +++--------- src/core/user/lang/en.json | 1 + src/core/user/pages/profile/profile.html | 29 ++- src/core/user/pages/profile/profile.scss | 22 ++ src/core/user/pages/profile/profile.ts | 30 ++- src/core/user/providers/delegate.ts | 161 +++++++++++- src/core/user/providers/user-handler.ts | 70 ++++++ src/core/user/user.module.ts | 8 +- src/theme/variables.scss | 2 + 12 files changed, 583 insertions(+), 143 deletions(-) rename src/addon/calendar/providers/{handlers.ts => mainmenu-handler.ts} (100%) create mode 100644 src/classes/delegate.ts create mode 100644 src/core/user/providers/user-handler.ts diff --git a/src/addon/calendar/calendar.module.ts b/src/addon/calendar/calendar.module.ts index a79aa07dd..b36d0bf0c 100644 --- a/src/addon/calendar/calendar.module.ts +++ b/src/addon/calendar/calendar.module.ts @@ -15,7 +15,7 @@ import { NgModule } from '@angular/core'; import { AddonCalendarProvider } from './providers/calendar'; import { AddonCalendarHelperProvider } from './providers/helper'; -import { AddonCalendarMainMenuHandler } from './providers/handlers'; +import { AddonCalendarMainMenuHandler } from './providers/mainmenu-handler'; import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate'; import { CoreInitDelegate } from '../../providers/init'; import { CoreLocalNotificationsProvider } from '../../providers/local-notifications'; diff --git a/src/addon/calendar/providers/handlers.ts b/src/addon/calendar/providers/mainmenu-handler.ts similarity index 100% rename from src/addon/calendar/providers/handlers.ts rename to src/addon/calendar/providers/mainmenu-handler.ts diff --git a/src/classes/delegate.ts b/src/classes/delegate.ts new file mode 100644 index 000000000..3185bf435 --- /dev/null +++ b/src/classes/delegate.ts @@ -0,0 +1,238 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { CoreLoggerProvider } from '../providers/logger'; +import { CoreSitesProvider } from '../providers/sites'; +import { CoreEventsProvider } from '../providers/events'; + +export interface CoreDelegateHandler { + /** + * Name of the handler, or name and sub context (mmaMessages, mmaMessage:blockContact, ...). + * @type {string} + */ + name: string; + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean|Promise; +}; + +/** + * Superclass to help creating delegates + */ +@Injectable() +export class CoreDelegate { + + /** + * Logger instance get from CoreLoggerProvider. + * @type {function} + */ + protected logger; + + /** + * List of registered handlers. + * @type {any} + */ + protected handlers: {[s: string]: CoreDelegateHandler} = {}; + + /** + * List of registered handlers enabled for the current site. + * @type {any} + */ + protected enabledHandlers: {[s: string]: CoreDelegateHandler} = {}; + + /** + * Default handler + * @type {CoreDelegateHandler} + */ + protected defaultHandler: CoreDelegateHandler; + + /** + * Time when last updateHandler functions started. + * @type {number} + */ + protected lastUpdateHandlersStart: number; + + /** + * Feature prefix to check is feature is enabled or disabled in site. + * This check is only made if not false. Override on the subclass or override isFeatureDisabled function. + * @type {string} + */ + protected featurePrefix: string; + + /** + * Constructor of the Delegate. + * + * @param {string} delegateName Delegate name used for logging purposes. + * @param {CoreLoggerProvider} loggerProvider CoreLoggerProvider instance, cannot be directly injected. + * @param {CoreSitesProvider} sitesProvider CoreSitesProvider instance, cannot be directly injected. + */ + constructor(delegateName: string, protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, + protected eventsProvider: CoreEventsProvider) { + this.logger = this.loggerProvider.getInstance(delegateName); + this.sitesProvider = sitesProvider; + + // Update handlers on this cases. + eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this)); + eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this)); + eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this)); + } + + /** + * Execute a certain function in a handler. + * If the handler isn't found or function isn't defined, call the same function in the default handler. + * + * @param {string} handlerName The handler name. + * @param {string} fnName Name of the function to execute. + * @param {any[]} params Parameters to pass to the function. + * @return {any} Function returned value or default value. + */ + protected executeFunction(handlerName: string, fnName: string, params?: any[]) : any { + let handler = this.enabledHandlers[handlerName]; + if (handler && handler[fnName]) { + return handler[fnName].apply(handler, params); + } else if (this.defaultHandler && this.defaultHandler[fnName]) { + return this.defaultHandler[fnName].apply(this, params); + } + } + + /** + * Check if a handler name has a registered handler (not necessarily enabled). + * + * @param {string} name The name of the handler. + * @return {boolean} If the controller is installed or not. + */ + hasHandler(name: string) : boolean { + return typeof this.handlers[name] !== 'undefined'; + } + + /** + * Check if a time belongs to the last update handlers call. + * This is to handle the cases where updateHandlers don't finish in the same order as they're called. + * + * @param {number} time Time to check. + * @return {boolean} Whether it's the last call. + */ + isLastUpdateCall(time: number) : boolean { + if (!this.lastUpdateHandlersStart) { + return true; + } + return time == this.lastUpdateHandlersStart; + } + + /** + * Register a profile handler. + */ + registerHandler(handler: CoreDelegateHandler) { + if (typeof this.handlers[handler.name] !== 'undefined') { + this.logger.log(`Addon '${handler.name}' already registered`); + return false; + } + + this.logger.log(`Registered addon '${handler.name}'`); + this.handlers[handler.name] = handler; + return true; + } + + /** + * Update the handler for the current site. + * + * @param {CoreDelegateHandler} handler The handler to check. + * @param {number} time Time this update process started. + * @return {Promise} Resolved when done. + */ + protected updateHandler(handler: CoreDelegateHandler, time: number) : Promise { + let promise, + siteId = this.sitesProvider.getCurrentSiteId(), + currentSite = this.sitesProvider.getCurrentSite(); + + if (!this.sitesProvider.isLoggedIn()) { + promise = Promise.reject(null); + } else if (this.isFeatureDisabled(handler, currentSite)) { + promise = Promise.resolve(false); + } else { + promise = Promise.resolve(handler.isEnabled()); + } + + // Checks if the handler is enabled. + return promise.catch(() => { + return false; + }).then((enabled: boolean) => { + // Verify that this call is the last one that was started. + // Check that site hasn't changed since the check started. + if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() === siteId) { + if (enabled) { + this.enabledHandlers[handler.name] = handler; + } else { + delete this.enabledHandlers[handler.name]; + } + } + }); + } + + /** + * Check if feature is enabled or disabled in the site, depending on the feature prefix and the handler name. + * + * @param {CoreDelegateHandler} handler Handler to check. + * @param {any} site Site to check. + * @return {boolean} Whether is enabled or disabled in site. + */ + protected isFeatureDisabled(handler: CoreDelegateHandler, site: any) : boolean{ + return typeof this.featurePrefix != "undefined" && site.isFeatureDisabled(this.featurePrefix + handler.name); + } + + /** + * Update the handlers for the current site. + * + * @return {Promise} Resolved when done. + */ + protected updateHandlers() : Promise { + let promises = [], + now = Date.now(); + + this.logger.debug('Updating handlers for current site.'); + + this.lastUpdateHandlersStart = now; + + // Loop over all the handlers. + for (let name in this.handlers) { + promises.push(this.updateHandler(this.handlers[name], now)); + } + + return Promise.all(promises).then(() => { + return true; + }, () => { + // Never reject. + return true; + }).then(() => { + + // Verify that this call is the last one that was started. + if (this.isLastUpdateCall(now)) { + this.updateData(); + } + }); + } + + /** + * Update handlers Data. + * Override this function to update handlers data. + */ + updateData() { + + } + +} \ No newline at end of file diff --git a/src/core/mainmenu/providers/delegate.ts b/src/core/mainmenu/providers/delegate.ts index a6edd6ee6..da5594cd5 100644 --- a/src/core/mainmenu/providers/delegate.ts +++ b/src/core/mainmenu/providers/delegate.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import { CoreEventsProvider } from '../../../providers/events'; +import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate'; import { CoreLoggerProvider } from '../../../providers/logger'; import { CoreSitesProvider } from '../../../providers/sites'; import { Subject, BehaviorSubject } from 'rxjs'; @@ -21,26 +22,13 @@ import { Subject, BehaviorSubject } from 'rxjs'; /** * Interface that all main menu handlers must implement. */ -export interface CoreMainMenuHandler { - /** - * Name of the handler. - * @type {string} - */ - name: string; - +export interface CoreMainMenuHandler extends CoreDelegateHandler { /** * The highest priority is displayed first. * @type {number} */ priority: number; - /** - * Whether or not the handler is enabled on a site level. - * - * @return {boolean|Promise} True or promise resolved with true if enabled. - */ - isEnabled(): boolean|Promise; - /** * Returns the data needed to render the handler. * @@ -96,20 +84,17 @@ export interface CoreMainMenuHandlerToDisplay extends CoreMainMenuHandlerData { * and notify an update in the data. */ @Injectable() -export class CoreMainMenuDelegate { - protected logger; +export class CoreMainMenuDelegate extends CoreDelegate { protected handlers: {[s: string]: CoreMainMenuHandler} = {}; protected enabledHandlers: {[s: string]: CoreMainMenuHandler} = {}; protected loaded = false; - protected lastUpdateHandlersStart: number; - protected siteHandlers: Subject = new BehaviorSubject([]); + protected siteHandlers: Subject = new BehaviorSubject([]); + protected featurePrefix = '$mmSideMenuDelegate_'; - constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider) { - this.logger = logger.getInstance('CoreMainMenuDelegate'); + constructor(protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, + protected eventsProvider: CoreEventsProvider) { + super('CoreMainMenuDelegate', loggerProvider, sitesProvider, eventsProvider); - eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this)); - eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this)); - eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this)); eventsProvider.on(CoreEventsProvider.LOGOUT, this.clearSiteHandlers.bind(this)); } @@ -133,131 +118,39 @@ export class CoreMainMenuDelegate { /** * Get the handlers for the current site. * - * @return {Subject} An observable that will receive the handlers. + * @return {Subject} An observable that will receive the handlers. */ getHandlers() : Subject { return this.siteHandlers; } /** - * Check if a time belongs to the last update handlers call. - * This is to handle the cases where updateHandlers don't finish in the same order as they're called. - * - * @param {number} time Time to check. - * @return {boolean} Whether it's the last call. + * Update handlers Data. */ - isLastUpdateCall(time: number) : boolean { - if (!this.lastUpdateHandlersStart) { - return true; - } - return time == this.lastUpdateHandlersStart; - } + updateData() { + let handlersData: any[] = []; - /** - * Register a handler. - * - * @param {CoreInitHandler} handler The handler to register. - * @return {boolean} True if registered successfully, false otherwise. - */ - registerHandler(handler: CoreMainMenuHandler) : boolean { - if (typeof this.handlers[handler.name] !== 'undefined') { - this.logger.log(`Addon '${handler.name}' already registered`); - return false; - } - this.logger.log(`Registered addon '${handler.name}'`); - this.handlers[handler.name] = handler; - return true; - } + for (let name in this.enabledHandlers) { + let handler = this.enabledHandlers[name], + data = handler.getDisplayData(); - /** - * Update the handler for the current site. - * - * @param {CoreInitHandler} handler The handler to check. - * @param {number} time Time this update process started. - * @return {Promise} Resolved when done. - */ - protected updateHandler(handler: CoreMainMenuHandler, time: number) : Promise { - let promise, - siteId = this.sitesProvider.getCurrentSiteId(), - currentSite = this.sitesProvider.getCurrentSite(); - - if (!this.sitesProvider.isLoggedIn()) { - promise = Promise.reject(null); - } else if (currentSite.isFeatureDisabled('$mmSideMenuDelegate_' + handler.name)) { - promise = Promise.resolve(false); - } else { - promise = Promise.resolve(handler.isEnabled()); + handlersData.push({ + data: data, + priority: handler.priority + }); } - // Checks if the handler is enabled. - return promise.catch(() => { - return false; - }).then((enabled: boolean) => { - // Verify that this call is the last one that was started. - // Check that site hasn't changed since the check started. - if (this.isLastUpdateCall(time) && this.sitesProvider.getCurrentSiteId() === siteId) { - if (enabled) { - this.enabledHandlers[handler.name] = handler; - } else { - delete this.enabledHandlers[handler.name]; - } - } + // Sort them by priority. + handlersData.sort((a, b) => { + return b.priority - a.priority; }); - } - /** - * Update the handlers for the current site. - * - * @return {Promise} Resolved when done. - */ - protected updateHandlers() : Promise { - let promises = [], - now = Date.now(); - - this.logger.debug('Updating handlers for current site.'); - - this.lastUpdateHandlersStart = now; - - // Loop over all the handlers. - for (let name in this.handlers) { - promises.push(this.updateHandler(this.handlers[name], now)); - } - - return Promise.all(promises).then(() => { - return true; - }, () => { - // Never reject. - return true; - }).then(() => { - // Verify that this call is the last one that was started. - if (this.isLastUpdateCall(now)) { - let handlersData: any[] = []; - - for (let name in this.enabledHandlers) { - let handler = this.enabledHandlers[name], - data: CoreMainMenuHandlerToDisplay = handler.getDisplayData(); - - data.name = handler.name; - - handlersData.push({ - data: data, - priority: handler.priority - }); - } - - // Sort them by priority. - handlersData.sort((a, b) => { - return b.priority - a.priority; - }); - - // Return only the display data. - let displayData = handlersData.map((item) => { - return item.data; - }); - - this.loaded = true; - this.siteHandlers.next(displayData); - } + // Return only the display data. + let displayData = handlersData.map((item) => { + return item.data; }); + + this.loaded = true; + this.siteHandlers.next(displayData); } } diff --git a/src/core/user/lang/en.json b/src/core/user/lang/en.json index 861570f59..6ea4eb277 100644 --- a/src/core/user/lang/en.json +++ b/src/core/user/lang/en.json @@ -15,6 +15,7 @@ "phone1": "Phone", "phone2": "Mobile phone", "roles": "Roles", + "sendemail": "Email", "student": "Student", "teacher": "Non-editing teacher", "webpage": "Web page" diff --git a/src/core/user/pages/profile/profile.html b/src/core/user/pages/profile/profile.html index d730e1182..efc7cba54 100644 --- a/src/core/user/pages/profile/profile.html +++ b/src/core/user/pages/profile/profile.html @@ -23,13 +23,40 @@

+ + + + + +

{{comHandler.title | translate}}

+
+
+ + + +
+
+ - + + + + +

{{ npHandler.title | translate }}

+
+ + + + diff --git a/src/core/user/pages/profile/profile.scss b/src/core/user/pages/profile/profile.scss index a5844f405..ff977a68f 100644 --- a/src/core/user/pages/profile/profile.scss +++ b/src/core/user/pages/profile/profile.scss @@ -5,4 +5,26 @@ page-core-user-profile { bottom: 30px; font-size: 24px; } + .core-user-communication-handlers { + padding: 6px 2px; + background: $list-background-color; + + .core-user-profile-handler { + background: $list-background-color; + border: 0; + color: $core-user-profile-communication-icons-color; + + .icon { + border-radius: 50%; + width: 32px; + height: 32px; + max-width: 32px; + font-size: 22px; + line-height: 32px; + color: white; + background-color: $core-user-profile-communication-icons-color; + margin-bottom: 5px; + } + } + } } \ No newline at end of file diff --git a/src/core/user/pages/profile/profile.ts b/src/core/user/pages/profile/profile.ts index 3d7bd28c7..143c5d850 100644 --- a/src/core/user/pages/profile/profile.ts +++ b/src/core/user/pages/profile/profile.ts @@ -23,6 +23,7 @@ import { CoreEventsProvider } from '../../../../providers/events'; import { CoreSitesProvider } from '../../../../providers/sites'; import { CoreMimetypeUtilsProvider } from '../../../../providers/utils/mimetype'; import { CoreFileUploaderHelperProvider } from '../../../fileuploader/providers/helper'; +import { CoreUserDelegate } from '../../providers/delegate'; /** * Page that displays an user profile page. @@ -44,11 +45,15 @@ export class CoreUserProfilePage { title: string; isDeleted: boolean = false; canChangeProfilePicture: boolean = false; + actionHandlers = []; + newPageHandlers = []; + communicationHandlers = []; constructor(private navParams: NavParams, private userProvider: CoreUserProvider, private userHelper: CoreUserHelperProvider, private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private eventsProvider: CoreEventsProvider, private coursesProvider: CoreCoursesProvider, private sitesProvider: CoreSitesProvider, - private mimetypeUtils: CoreMimetypeUtilsProvider, private fileUploaderHelper: CoreFileUploaderHelperProvider) { + private mimetypeUtils: CoreMimetypeUtilsProvider, private fileUploaderHelper: CoreFileUploaderHelperProvider, + private userDelegate: CoreUserDelegate) { this.userId = navParams.get('userId'); this.courseId = navParams.get('courseId'); @@ -97,6 +102,29 @@ export class CoreUserProfilePage { this.isLoadingHandlers = true; + this.userDelegate.getProfileHandlersFor(user, this.courseId).then((handlers) => { + console.error(handlers); + this.actionHandlers = []; + this.newPageHandlers = []; + this.communicationHandlers = []; + handlers.forEach((handler) => { + switch (handler.type) { + case CoreUserDelegate.TYPE_COMMUNICATION: + this.communicationHandlers.push(handler.data); + break; + case CoreUserDelegate.TYPE_ACTION: + this.actionHandlers.push(handler.data); + break; + case CoreUserDelegate.TYPE_NEW_PAGE: + default: + this.newPageHandlers.push(handler.data); + break; + } + }); + }).finally(() => { + this.isLoadingHandlers = false; + }); + }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'core.user.errorloaduser', true); }); diff --git a/src/core/user/providers/delegate.ts b/src/core/user/providers/delegate.ts index b2ea852eb..7bb2f7638 100644 --- a/src/core/user/providers/delegate.ts +++ b/src/core/user/providers/delegate.ts @@ -13,18 +13,171 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate'; +import { CoreCoursesProvider } from '../../../core/courses/providers/courses'; import { CoreLoggerProvider } from '../../../providers/logger'; +import { CoreSitesProvider } from '../../../providers/sites'; +import { CoreEventsProvider } from '../../../providers/events'; + +export interface CoreUserProfileHandler extends CoreDelegateHandler { + /** + * The highest priority is displayed first. + * @type {number} + */ + priority: number; + + /** + * A type should be specified among these: + * - TYPE_COMMUNICATION: will be displayed under the user avatar. Should have icon. Spinner not used. + * - TYPE_NEW_PAGE: will be displayed as a list of items. Should have icon. Spinner not used. + * Default value if none is specified. + * - TYPE_ACTION: will be displayed as a button and should not redirect to any state. Spinner use is recommended. + * @type {string} + */ + type: string; + + /** + * Whether or not the handler is enabled for a user. + * @param {any} user User object. + * @param {number} courseId Course ID where to show. + * @param {any} [navOptions] Navigation options for the course. + * @param {any} [admOptions] Admin options for the course. + * @return {boolean|Promise} Whether or not the handler is enabled for a user. + */ + isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean|Promise; + + /** + * Returns the data needed to render the handler. + * @param {any} user User object. + * @param {number} courseId Course ID where to show. + * @return {CoreUserProfileHandlerData} Data to be shown. + */ + getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData; +}; + +export interface CoreUserProfileHandlerData { + /** + * Title to display. + * @type {string} + */ + title: string; + + /** + * Name of the icon to display. Mandatory for TYPE_COMMUNICATION. + * @type {string} + */ + icon?: string; + + /** + * Additional class to add to the HTML. + * @type {string} + */ + class: string; + + /** + * If enabled, element will be hidden. Only for TYPE_NEW_PAGE and TYPE_ACTION. + * @type {boolean} + */ + hidden?: boolean; + + /** + * If enabled will show an spinner. Only for TYPE_ACTION. + * @type {boolean} + */ + spinner?: boolean; + + /** + * Action to do when clicked. + * @param {any} $event + * @param {any} user User object. + * @param {number} courseId Course ID where to show. + * @return {any} Action to be done. + */ + action?($event: any, user: any, courseId: number): any; +}; /** * Service to interact with plugins to be shown in user profile. Provides functions to register a plugin * and notify an update in the data. */ @Injectable() -export class CoreUserDelegate { - protected logger; +export class CoreUserDelegate extends CoreDelegate { + /** + * User profile handler type for communication. + * @type {string} + */ + public static TYPE_COMMUNICATION = 'communication'; - constructor(logger: CoreLoggerProvider) { - this.logger = logger.getInstance('CoreUserDelegate'); + /** + * User profile handler type for new page. + * @type {string} + */ + public static TYPE_NEW_PAGE = 'newpage'; + /** + * User profile handler type for actions. + * @type {string} + */ + public static TYPE_ACTION = 'action'; + + protected handlers: {[s: string]: CoreUserProfileHandler} = {}; + protected enabledHandlers: {[s: string]: CoreUserProfileHandler} = {}; + protected featurePrefix = '$mmUserDelegate_'; + + constructor(protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, + private coursesProvider: CoreCoursesProvider, protected eventsProvider: CoreEventsProvider) { + super('CoreUserDelegate', loggerProvider, sitesProvider, eventsProvider); } + /** + * Get the profile handlers for a user. + * + * @param {any} user The user object. + * @param {number} courseId The course ID. + * @return {Promise} Resolved with an array of objects containing 'priority', 'data' and 'type'. + */ + getProfileHandlersFor(user: any, courseId): Promise { + let handlers = [], + promises = []; + + // Retrieve course options forcing cache. + return this.coursesProvider.getUserCourses(true).then((courses) => { + let courseIds = courses.map((course) => { + return course.id; + }); + + return this.coursesProvider.getCoursesOptions(courseIds).then((options) => { + // For backwards compatibility we don't modify the courseId. + let courseIdForOptions = courseId || this.sitesProvider.getSiteHomeId(), + navOptions = options.navOptions[courseIdForOptions], + admOptions = options.admOptions[courseIdForOptions]; + + for (let name in this.enabledHandlers) { + // Checks if the handler is enabled for the user. + let handler = this.handlers[name], + isEnabledForUser = handler.isEnabledForUser(user, courseId, navOptions, admOptions), + promise = Promise.resolve(isEnabledForUser).then((enabled) => { + if (enabled) { + handlers.push({ + data: handler.getDisplayData(user, courseId), + priority: handler.priority, + type: handler.type || CoreUserDelegate.TYPE_NEW_PAGE + }); + } else { + return Promise.reject(null); + } + }).catch(function() { + // Nothing to do here, it is not enabled for this user. + }); + promises.push(promise); + } + + return Promise.all(promises).then(() => { + return handlers; + }); + }); + }).catch(function() { + // Never fails. + return handlers; + }); + } } diff --git a/src/core/user/providers/user-handler.ts b/src/core/user/providers/user-handler.ts new file mode 100644 index 000000000..3a37255e1 --- /dev/null +++ b/src/core/user/providers/user-handler.ts @@ -0,0 +1,70 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from './delegate'; +import { CoreSitesProvider } from '../../../providers/sites'; + +/** + * Profile links email handler. + */ +@Injectable() +export class CoreUserProfileMailHandler implements CoreUserProfileHandler { + name = 'mmUser'; + priority = 700; + type = CoreUserDelegate.TYPE_COMMUNICATION; + + constructor(protected sitesProvider: CoreSitesProvider) {} + + /** + * Check if handler is enabled. + * + * @return {boolean} Always enabled. + */ + isEnabled(): boolean { + return true; + } + + /** + * Check if handler is enabled for this user in this context. + * + * @param {any} user User to check. + * @param {number} courseId Course ID. + * @param {any} [navOptions] Course navigation options for current user. See $mmCourses#getUserNavigationOptions. + * @param {any} [admOptions] Course admin options for current user. See $mmCourses#getUserAdministrationOptions. + * @return {boolean|Promise} Promise resolved with true if enabled, resolved with false otherwise. + */ + isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean|Promise { + // Not current user required. + return user.id != this.sitesProvider.getCurrentSite().getUserId() && user.email; + }; + + /** + * Returns the data needed to render the handler. + * + * @return {CoreUserProfileHandlerData} Data needed to render the handler. + */ + getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData { + return { + icon: 'mail', + title: 'core.user.sendemail', + class: 'core-user-profile-mail', + action: ($event, user, courseId) => { + $event.preventDefault(); + $event.stopPropagation(); + window.location.href = "mailto:" + user.email; + } + }; + } +} diff --git a/src/core/user/user.module.ts b/src/core/user/user.module.ts index 2aeae9a9e..3f052aa1d 100644 --- a/src/core/user/user.module.ts +++ b/src/core/user/user.module.ts @@ -16,6 +16,7 @@ import { NgModule } from '@angular/core'; import { CoreUserDelegate } from './providers/delegate'; import { CoreUserProvider } from './providers/user'; import { CoreUserHelperProvider } from './providers/helper'; +import { CoreUserProfileMailHandler } from './providers/user-handler'; @NgModule({ declarations: [ @@ -24,8 +25,13 @@ import { CoreUserHelperProvider } from './providers/helper'; ], providers: [ CoreUserDelegate, + CoreUserProfileMailHandler, CoreUserProvider, CoreUserHelperProvider ] }) -export class CoreUserModule {} +export class CoreUserModule { + constructor(userDelegate: CoreUserDelegate, userProfileMailHandler: CoreUserProfileMailHandler) { + userDelegate.registerHandler(userProfileMailHandler); + } +} diff --git a/src/theme/variables.scss b/src/theme/variables.scss index a3e26a43a..ffaec06dd 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -195,3 +195,5 @@ $core-top-tabs-background: $white; $core-top-tabs-color: $gray-dark; $core-top-tabs-border: $gray; $core-top-tabs-color-active: $core-color; + +$core-user-profile-communication-icons-color: $core-color; From ba1267410e83d52f3bb4445a34e158f7ff4ac09e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 16 Jan 2018 15:52:07 +0100 Subject: [PATCH 05/13] MOBILE-2317 user: Add profile field delegate --- .../checkbox/checkbox.module.ts | 46 +++++ .../checkbox/component/checkbox.html | 16 ++ .../checkbox/component/checkbox.scss | 0 .../checkbox/component/checkbox.ts | 47 +++++ .../checkbox/providers/handler.ts | 70 ++++++++ .../datetime/component/datetime.html | 10 ++ .../datetime/component/datetime.scss | 0 .../datetime/component/datetime.ts | 63 +++++++ .../datetime/datetime.module.ts | 48 +++++ .../datetime/providers/handler.ts | 85 +++++++++ .../userprofilefield/menu/component/menu.html | 13 ++ .../userprofilefield/menu/component/menu.scss | 0 .../userprofilefield/menu/component/menu.ts | 55 ++++++ .../userprofilefield/menu/menu.module.ts | 48 +++++ .../menu/providers/handler.ts | 70 ++++++++ .../userprofilefield/text/component/text.html | 10 ++ .../userprofilefield/text/component/text.scss | 0 .../userprofilefield/text/component/text.ts | 55 ++++++ .../text/providers/handler.ts | 69 ++++++++ .../userprofilefield/text/text.module.ts | 48 +++++ .../textarea/component/textarea.html | 10 ++ .../textarea/component/textarea.scss | 0 .../textarea/component/textarea.ts | 50 ++++++ .../textarea/providers/handler.ts | 83 +++++++++ .../textarea/textarea.module.ts | 48 +++++ .../userprofilefield.module.ts | 35 ++++ src/app/app.module.ts | 5 +- src/core/user/components/components.module.ts | 36 ++++ .../user-profile-field.html | 2 + .../user-profile-field.scss | 5 + .../user-profile-field/user-profile-field.ts | 98 +++++++++++ src/core/user/pages/about/about.html | 1 + src/core/user/pages/about/about.module.ts | 2 + src/core/user/pages/profile/profile.ts | 3 +- .../{delegate.ts => user-delegate.ts} | 2 +- src/core/user/providers/user-handler.ts | 2 +- .../providers/user-profile-field-delegate.ts | 164 ++++++++++++++++++ src/core/user/user.module.ts | 4 +- 38 files changed, 1297 insertions(+), 6 deletions(-) create mode 100644 src/addon/userprofilefield/checkbox/checkbox.module.ts create mode 100644 src/addon/userprofilefield/checkbox/component/checkbox.html create mode 100644 src/addon/userprofilefield/checkbox/component/checkbox.scss create mode 100644 src/addon/userprofilefield/checkbox/component/checkbox.ts create mode 100644 src/addon/userprofilefield/checkbox/providers/handler.ts create mode 100644 src/addon/userprofilefield/datetime/component/datetime.html create mode 100644 src/addon/userprofilefield/datetime/component/datetime.scss create mode 100644 src/addon/userprofilefield/datetime/component/datetime.ts create mode 100644 src/addon/userprofilefield/datetime/datetime.module.ts create mode 100644 src/addon/userprofilefield/datetime/providers/handler.ts create mode 100644 src/addon/userprofilefield/menu/component/menu.html create mode 100644 src/addon/userprofilefield/menu/component/menu.scss create mode 100644 src/addon/userprofilefield/menu/component/menu.ts create mode 100644 src/addon/userprofilefield/menu/menu.module.ts create mode 100644 src/addon/userprofilefield/menu/providers/handler.ts create mode 100644 src/addon/userprofilefield/text/component/text.html create mode 100644 src/addon/userprofilefield/text/component/text.scss create mode 100644 src/addon/userprofilefield/text/component/text.ts create mode 100644 src/addon/userprofilefield/text/providers/handler.ts create mode 100644 src/addon/userprofilefield/text/text.module.ts create mode 100644 src/addon/userprofilefield/textarea/component/textarea.html create mode 100644 src/addon/userprofilefield/textarea/component/textarea.scss create mode 100644 src/addon/userprofilefield/textarea/component/textarea.ts create mode 100644 src/addon/userprofilefield/textarea/providers/handler.ts create mode 100644 src/addon/userprofilefield/textarea/textarea.module.ts create mode 100644 src/addon/userprofilefield/userprofilefield.module.ts create mode 100644 src/core/user/components/components.module.ts create mode 100644 src/core/user/components/user-profile-field/user-profile-field.html create mode 100644 src/core/user/components/user-profile-field/user-profile-field.scss create mode 100644 src/core/user/components/user-profile-field/user-profile-field.ts rename src/core/user/providers/{delegate.ts => user-delegate.ts} (98%) create mode 100644 src/core/user/providers/user-profile-field-delegate.ts diff --git a/src/addon/userprofilefield/checkbox/checkbox.module.ts b/src/addon/userprofilefield/checkbox/checkbox.module.ts new file mode 100644 index 000000000..1d85b6890 --- /dev/null +++ b/src/addon/userprofilefield/checkbox/checkbox.module.ts @@ -0,0 +1,46 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddonUserProfileFieldCheckboxHandler } from './providers/handler'; +import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate'; +import { AddonUserProfileFieldCheckboxComponent } from './component/checkbox'; +import { CoreComponentsModule } from '../../../components/components.module'; + +@NgModule({ + declarations: [ + AddonUserProfileFieldCheckboxComponent + ], + imports: [ + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule + ], + providers: [ + AddonUserProfileFieldCheckboxHandler + ], + exports: [ + AddonUserProfileFieldCheckboxComponent + ], + entryComponents: [ + AddonUserProfileFieldCheckboxComponent + ] +}) +export class AddonUserProfileFieldCheckboxModule { + constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldCheckboxHandler) { + userProfileFieldDelegate.registerHandler(handler); + } +} \ No newline at end of file diff --git a/src/addon/userprofilefield/checkbox/component/checkbox.html b/src/addon/userprofilefield/checkbox/component/checkbox.html new file mode 100644 index 000000000..8ab6fe45f --- /dev/null +++ b/src/addon/userprofilefield/checkbox/component/checkbox.html @@ -0,0 +1,16 @@ + + +

{{ field.name }}

+

+ {{ 'core.yes' | translate }} +

+

+ {{ 'core.no' | translate }} +

+
+ + + {{ field.name }} + + + \ No newline at end of file diff --git a/src/addon/userprofilefield/checkbox/component/checkbox.scss b/src/addon/userprofilefield/checkbox/component/checkbox.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/addon/userprofilefield/checkbox/component/checkbox.ts b/src/addon/userprofilefield/checkbox/component/checkbox.ts new file mode 100644 index 000000000..28f11a4bf --- /dev/null +++ b/src/addon/userprofilefield/checkbox/component/checkbox.ts @@ -0,0 +1,47 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; + +/** + * Directive to render a checkbox user profile field. + */ +@Component({ + selector: 'core-user-profile-field-checkbox', + templateUrl: 'checkbox.html' +}) +export class AddonUserProfileFieldCheckboxComponent implements OnInit { + @Input() field: any; // The profile field to be rendered. + @Input() edit?: boolean = false; // True if editing the field. Defaults to false. + @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + + constructor() {} + + /** + * Component being initialized. + */ + ngOnInit() { + let field = this.field; + + if (field && this.edit && this.model) { + field.modelName = 'profile_field_' + field.shortname; + + // Initialize the value. + if (typeof field.defaultdata != 'undefined' && typeof this.model[field.modelName] == 'undefined') { + this.model[field.modelName] = field.defaultdata && field.defaultdata !== '0' && field.defaultdata !== 'false'; + } + } + } + +} \ No newline at end of file diff --git a/src/addon/userprofilefield/checkbox/providers/handler.ts b/src/addon/userprofilefield/checkbox/providers/handler.ts new file mode 100644 index 000000000..69c7ac051 --- /dev/null +++ b/src/addon/userprofilefield/checkbox/providers/handler.ts @@ -0,0 +1,70 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '../../../../core/user/providers/user-profile-field-delegate'; +import { AddonUserProfileFieldCheckboxComponent } from '../component/checkbox'; + +/** + * Checkbox user profile field handlers. + */ +@Injectable() +export class AddonUserProfileFieldCheckboxHandler implements CoreUserProfileFieldHandler { + name = 'checkbox'; + + constructor() {} + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled() : boolean|Promise { + return true; + } + + /** + * Get the data to send for the field based on the input data. + * + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @param {any} model Model with the input data. + * @return {CoreUserProfileFieldHandlerData} Data to send for the field. + */ + getData(field: any, signup: boolean, registerAuth: string, model: any): CoreUserProfileFieldHandlerData { + let name = 'profile_field_' + field.shortname; + + if (typeof model[name] != 'undefined') { + return { + type: 'checkbox', + name: name, + value: model[name] ? 1 : 0 + }; + } + } + + /** + * Return the Component to use to display the user profile field. + * + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @return {any} The component to use, undefined if not found. + */ + getComponent(field: any, signup: boolean, registerAuth: string) { + return AddonUserProfileFieldCheckboxComponent; + } + +} \ No newline at end of file diff --git a/src/addon/userprofilefield/datetime/component/datetime.html b/src/addon/userprofilefield/datetime/component/datetime.html new file mode 100644 index 000000000..cd19d4595 --- /dev/null +++ b/src/addon/userprofilefield/datetime/component/datetime.html @@ -0,0 +1,10 @@ + + +

{{ field.name }}

+

{{ field.value * 1000 | coreFormatDate:"dfmediumdate"}}

+
+ + + {{ field.name }} + + \ No newline at end of file diff --git a/src/addon/userprofilefield/datetime/component/datetime.scss b/src/addon/userprofilefield/datetime/component/datetime.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/addon/userprofilefield/datetime/component/datetime.ts b/src/addon/userprofilefield/datetime/component/datetime.ts new file mode 100644 index 000000000..4526abc5e --- /dev/null +++ b/src/addon/userprofilefield/datetime/component/datetime.ts @@ -0,0 +1,63 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; + +/** + * Directive to render a datetime user profile field. + */ +@Component({ + selector: 'core-user-profile-field-datetime', + templateUrl: 'datetime.html' +}) +export class AddonUserProfileFieldDatetimeComponent implements OnInit { + @Input() field: any; // The profile field to be rendered. + @Input() edit?: boolean = false; // True if editing the field. Defaults to false. + @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + + + constructor() {} + + /** + * Component being initialized. + */ + ngOnInit() { + let field = this.field, + year; + if (field && this.edit && this.model) { + field.modelName = 'profile_field_' + field.shortname; + + // Check if it's only date or it has time too. + field.hasTime = field.param3 && field.param3 !== '0' && field.param3 !== 'false'; + field.format = field.hasTime ? 'core.dffulldate' : 'core.dfdaymonthyear'; + + // Check min value. + if (field.param1) { + year = parseInt(field.param1, 10); + if (year) { + field.min = year; + } + } + + // Check max value. + if (field.param2) { + year = parseInt(field.param2, 10); + if (year) { + field.max = year; + } + } + } + } + +} \ No newline at end of file diff --git a/src/addon/userprofilefield/datetime/datetime.module.ts b/src/addon/userprofilefield/datetime/datetime.module.ts new file mode 100644 index 000000000..1697b0e9f --- /dev/null +++ b/src/addon/userprofilefield/datetime/datetime.module.ts @@ -0,0 +1,48 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddonUserProfileFieldDatetimeHandler } from './providers/handler'; +import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate'; +import { AddonUserProfileFieldDatetimeComponent } from './component/datetime'; +import { CoreComponentsModule } from '../../../components/components.module'; +import { CorePipesModule } from '../../../pipes/pipes.module'; + +@NgModule({ + declarations: [ + AddonUserProfileFieldDatetimeComponent + ], + imports: [ + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CorePipesModule + ], + providers: [ + AddonUserProfileFieldDatetimeHandler + ], + exports: [ + AddonUserProfileFieldDatetimeComponent + ], + entryComponents: [ + AddonUserProfileFieldDatetimeComponent + ] +}) +export class AddonUserProfileFieldDatetimeModule { + constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldDatetimeHandler) { + userProfileFieldDelegate.registerHandler(handler); + } +} \ No newline at end of file diff --git a/src/addon/userprofilefield/datetime/providers/handler.ts b/src/addon/userprofilefield/datetime/providers/handler.ts new file mode 100644 index 000000000..ad3c95414 --- /dev/null +++ b/src/addon/userprofilefield/datetime/providers/handler.ts @@ -0,0 +1,85 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '../../../../core/user/providers/user-profile-field-delegate'; +import { AddonUserProfileFieldDatetimeComponent } from '../component/datetime'; +import { Platform } from 'ionic-angular'; + +/** + * Datetime user profile field handlers. + */ +@Injectable() +export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFieldHandler { + name = 'datetime'; + + constructor(private platform: Platform) {} + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled() : boolean|Promise { + return true; + } + + /** + * Get the data to send for the field based on the input data. + * + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @param {any} model Model with the input data. + * @return {CoreUserProfileFieldHandlerData} Data to send for the field. + */ + getData(field: any, signup: boolean, registerAuth: string, model: any): CoreUserProfileFieldHandlerData { + let hasTime = field.param3 && field.param3 !== '0' && field.param3 !== 'false', + modelName = 'profile_field_' + field.shortname, + date = JSON.parse(JSON.stringify(model[modelName + '_date'])), + time; + + if (date) { + if (hasTime && this.platform.is('ios')) { + // In iOS the time is in a different input. Add it to the date. + time = model[modelName + '_time']; + if (!time) { + return; + } + + date.setHours(time.getHours()); + date.setMinutes(time.getMinutes()); + } + + return { + type: 'datetime', + name: 'profile_field_' + field.shortname, + value: Math.round(date.getTime() / 1000) + }; + } + } + + /** + * Return the Component to use to display the user profile field. + * + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @return {any} The component to use, undefined if not found. + */ + getComponent(field: any, signup: boolean, registerAuth: string) { + return AddonUserProfileFieldDatetimeComponent; + } + +} \ No newline at end of file diff --git a/src/addon/userprofilefield/menu/component/menu.html b/src/addon/userprofilefield/menu/component/menu.html new file mode 100644 index 000000000..23ee49f9b --- /dev/null +++ b/src/addon/userprofilefield/menu/component/menu.html @@ -0,0 +1,13 @@ + + +

{{ field.name }}

+

+
+ + + {{ field.name }} + + {{ 'core.choosedots' | translate }} + {{option}} + + diff --git a/src/addon/userprofilefield/menu/component/menu.scss b/src/addon/userprofilefield/menu/component/menu.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/addon/userprofilefield/menu/component/menu.ts b/src/addon/userprofilefield/menu/component/menu.ts new file mode 100644 index 000000000..98bf2f43b --- /dev/null +++ b/src/addon/userprofilefield/menu/component/menu.ts @@ -0,0 +1,55 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; + +/** + * Directive to render a menu user profile field. + */ +@Component({ + selector: 'core-user-profile-field-menu', + templateUrl: 'menu.html' +}) +export class AddonUserProfileFieldMenuComponent implements OnInit { + @Input() field: any; // The profile field to be rendered. + @Input() edit?: boolean = false; // True if editing the field. Defaults to false. + @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + + constructor() {} + + /** + * Component being initialized. + */ + ngOnInit() { + let field = this.field; + + if (field && this.edit && this.model) { + field.modelName = 'profile_field_' + field.shortname; + + // Parse options. + if (field.param1) { + field.options = field.param1.split(/\r\n|\r|\n/g); + } else { + field.options = []; + } + + // Initialize the value using default data. + if (typeof field.defaultdata != 'undefined' && typeof this.model[field.modelName] == 'undefined') { + this.model[field.modelName] = field.defaultdata; + } + } + + } + +} \ No newline at end of file diff --git a/src/addon/userprofilefield/menu/menu.module.ts b/src/addon/userprofilefield/menu/menu.module.ts new file mode 100644 index 000000000..dfaa1c0ea --- /dev/null +++ b/src/addon/userprofilefield/menu/menu.module.ts @@ -0,0 +1,48 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddonUserProfileFieldMenuHandler } from './providers/handler'; +import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate'; +import { AddonUserProfileFieldMenuComponent } from './component/menu'; +import { CoreComponentsModule } from '../../../components/components.module'; +import { CoreDirectivesModule } from '../../../directives/directives.module'; + +@NgModule({ + declarations: [ + AddonUserProfileFieldMenuComponent + ], + imports: [ + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule + ], + providers: [ + AddonUserProfileFieldMenuHandler + ], + exports: [ + AddonUserProfileFieldMenuComponent + ], + entryComponents: [ + AddonUserProfileFieldMenuComponent + ] +}) +export class AddonUserProfileFieldMenuModule { + constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldMenuHandler) { + userProfileFieldDelegate.registerHandler(handler); + } +} \ No newline at end of file diff --git a/src/addon/userprofilefield/menu/providers/handler.ts b/src/addon/userprofilefield/menu/providers/handler.ts new file mode 100644 index 000000000..470fd1d3d --- /dev/null +++ b/src/addon/userprofilefield/menu/providers/handler.ts @@ -0,0 +1,70 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '../../../../core/user/providers/user-profile-field-delegate'; +import { AddonUserProfileFieldMenuComponent } from '../component/menu'; + +/** + * Menu user profile field handlers. + */ +@Injectable() +export class AddonUserProfileFieldMenuHandler implements CoreUserProfileFieldHandler { + name = 'menu'; + + constructor() {} + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled() : boolean|Promise { + return true; + } + + /** + * Get the data to send for the field based on the input data. + * + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @param {any} model Model with the input data. + * @return {CoreUserProfileFieldHandlerData} Data to send for the field. + */ + getData(field: any, signup: boolean, registerAuth: string, model: any): CoreUserProfileFieldHandlerData { + let name = 'profile_field_' + field.shortname; + + if (model[name]) { + return { + type: 'menu', + name: name, + value: model[name] + }; + } + } + + /** + * Return the Component to use to display the user profile field. + * + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @return {any} The component to use, undefined if not found. + */ + getComponent(field: any, signup: boolean, registerAuth: string) { + return AddonUserProfileFieldMenuComponent; + } + +} \ No newline at end of file diff --git a/src/addon/userprofilefield/text/component/text.html b/src/addon/userprofilefield/text/component/text.html new file mode 100644 index 000000000..c27315320 --- /dev/null +++ b/src/addon/userprofilefield/text/component/text.html @@ -0,0 +1,10 @@ + + +

{{ field.name }}

+

+
+ + + {{ field.name }} + + diff --git a/src/addon/userprofilefield/text/component/text.scss b/src/addon/userprofilefield/text/component/text.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/addon/userprofilefield/text/component/text.ts b/src/addon/userprofilefield/text/component/text.ts new file mode 100644 index 000000000..f8fd5b72e --- /dev/null +++ b/src/addon/userprofilefield/text/component/text.ts @@ -0,0 +1,55 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; + +/** + * Directive to render a text user profile field. + */ +@Component({ + selector: 'core-user-profile-field-text', + templateUrl: 'text.html' +}) +export class AddonUserProfileFieldTextComponent implements OnInit { + @Input() field: any; // The profile field to be rendered. + @Input() edit?: boolean = false; // True if editing the field. Defaults to false. + @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + + constructor() {} + + /** + * Component being initialized. + */ + ngOnInit() { + let field = this.field; + + if (field && this.edit && this.model) { + field.modelName = 'profile_field_' + field.shortname; + + // Check max length. + if (field.param2) { + field.maxlength = parseInt(field.param2, 10) || ''; + } + + // Check if it's a password or text. + field.inputType = field.param3 && field.param3 !== '0' && field.param3 !== 'false' ? 'password' : 'text'; + + // Initialize the value using default data. + if (typeof field.defaultdata != 'undefined' && typeof this.model[field.modelName] == 'undefined') { + this.model[field.modelName] = field.defaultdata; + } + } + } + +} \ No newline at end of file diff --git a/src/addon/userprofilefield/text/providers/handler.ts b/src/addon/userprofilefield/text/providers/handler.ts new file mode 100644 index 000000000..30949f452 --- /dev/null +++ b/src/addon/userprofilefield/text/providers/handler.ts @@ -0,0 +1,69 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '../../../../core/user/providers/user-profile-field-delegate'; +import { AddonUserProfileFieldTextComponent } from '../component/text'; +import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; + +/** + * Text user profile field handlers. + */ +@Injectable() +export class AddonUserProfileFieldTextHandler implements CoreUserProfileFieldHandler { + name = 'text'; + + constructor(private textUtils: CoreTextUtilsProvider) {} + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled() : boolean|Promise { + return true; + } + + /** + * Get the data to send for the field based on the input data. + * + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @param {any} model Model with the input data. + * @return {CoreUserProfileFieldHandlerData} Data to send for the field. + */ + getData(field: any, signup: boolean, registerAuth: string, model: any): CoreUserProfileFieldHandlerData { + let name = 'profile_field_' + field.shortname; + + return { + type: 'text', + name: name, + value: this.textUtils.cleanTags(model[name]) + }; + } + + /** + * Return the Component to use to display the user profile field. + * + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @return {any} The component to use, undefined if not found. + */ + getComponent(field: any, signup: boolean, registerAuth: string) { + return AddonUserProfileFieldTextComponent; + } + +} \ No newline at end of file diff --git a/src/addon/userprofilefield/text/text.module.ts b/src/addon/userprofilefield/text/text.module.ts new file mode 100644 index 000000000..4264c858c --- /dev/null +++ b/src/addon/userprofilefield/text/text.module.ts @@ -0,0 +1,48 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddonUserProfileFieldTextHandler } from './providers/handler'; +import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate'; +import { AddonUserProfileFieldTextComponent } from './component/text'; +import { CoreComponentsModule } from '../../../components/components.module'; +import { CoreDirectivesModule } from '../../../directives/directives.module'; + +@NgModule({ + declarations: [ + AddonUserProfileFieldTextComponent + ], + imports: [ + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule + ], + providers: [ + AddonUserProfileFieldTextHandler + ], + exports: [ + AddonUserProfileFieldTextComponent + ], + entryComponents: [ + AddonUserProfileFieldTextComponent + ] +}) +export class AddonUserProfileFieldTextModule { + constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldTextHandler) { + userProfileFieldDelegate.registerHandler(handler); + } +} \ No newline at end of file diff --git a/src/addon/userprofilefield/textarea/component/textarea.html b/src/addon/userprofilefield/textarea/component/textarea.html new file mode 100644 index 000000000..003d70ef2 --- /dev/null +++ b/src/addon/userprofilefield/textarea/component/textarea.html @@ -0,0 +1,10 @@ + + +

{{ field.name }}

+

+
+ + + {{ field.name }} + + diff --git a/src/addon/userprofilefield/textarea/component/textarea.scss b/src/addon/userprofilefield/textarea/component/textarea.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/addon/userprofilefield/textarea/component/textarea.ts b/src/addon/userprofilefield/textarea/component/textarea.ts new file mode 100644 index 000000000..967c4081c --- /dev/null +++ b/src/addon/userprofilefield/textarea/component/textarea.ts @@ -0,0 +1,50 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; + +/** + * Directive to render a textarea user profile field. + */ +@Component({ + selector: 'core-user-profile-field-textarea', + templateUrl: 'textarea.html' +}) +export class AddonUserProfileFieldTextareaComponent implements OnInit { + @Input() field: any; // The profile field to be rendered. + @Input() edit?: boolean = false; // True if editing the field. Defaults to false. + @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + + constructor() {} + + /** + * Component being initialized. + */ + ngOnInit() { + let field = this.field; + + if (field && this.edit && this.model) { + field.modelName = 'profile_field_' + field.shortname; + this.model[field.modelName] = { + format: 1 + }; + + // Initialize the value using default data. + if (typeof field.defaultdata != 'undefined' && typeof this.model[field.modelName].text == 'undefined') { + this.model[field.modelName].text = field.defaultdata; + } + } + } + +} \ No newline at end of file diff --git a/src/addon/userprofilefield/textarea/providers/handler.ts b/src/addon/userprofilefield/textarea/providers/handler.ts new file mode 100644 index 000000000..544c2ad3e --- /dev/null +++ b/src/addon/userprofilefield/textarea/providers/handler.ts @@ -0,0 +1,83 @@ + +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '../../../../core/user/providers/user-profile-field-delegate'; +import { AddonUserProfileFieldTextareaComponent } from '../component/textarea'; +import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; + +/** + * Textarea user profile field handlers. + */ +@Injectable() +export class AddonUserProfileFieldTextareaHandler implements CoreUserProfileFieldHandler { + name = 'textarea'; + + constructor(private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider) {} + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabled() : boolean|Promise { + return true; + } + + /** + * Get the data to send for the field based on the input data. + * + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @param {any} model Model with the input data. + * @return {Promise} Data to send for the field. + */ + getData(field: any, signup: boolean, registerAuth: string, model: any): Promise { + let name = 'profile_field_' + field.shortname; + + if (model[name]) { + return this.domUtils.isRichTextEditorEnabled().then((enabled) => { + let text = model[name].text || ''; + if (!enabled) { + // Rich text editor not enabled, add some HTML to the message if needed. + text = this.textUtils.formatHtmlLines(text); + } + + return { + type: 'textarea', + name: name, + value: JSON.stringify({ + text: text, + format: model[name].format || 1 + }) + }; + }); + } + } + + /** + * Return the Component to use to display the user profile field. + * + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @return {any} The component to use, undefined if not found. + */ + getComponent(field: any, signup: boolean, registerAuth: string) { + return AddonUserProfileFieldTextareaComponent; + } + +} \ No newline at end of file diff --git a/src/addon/userprofilefield/textarea/textarea.module.ts b/src/addon/userprofilefield/textarea/textarea.module.ts new file mode 100644 index 000000000..b1c2212d3 --- /dev/null +++ b/src/addon/userprofilefield/textarea/textarea.module.ts @@ -0,0 +1,48 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddonUserProfileFieldTextareaHandler } from './providers/handler'; +import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate'; +import { AddonUserProfileFieldTextareaComponent } from './component/textarea'; +import { CoreComponentsModule } from '../../../components/components.module'; +import { CoreDirectivesModule } from '../../../directives/directives.module'; + +@NgModule({ + declarations: [ + AddonUserProfileFieldTextareaComponent + ], + imports: [ + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule + ], + providers: [ + AddonUserProfileFieldTextareaHandler + ], + exports: [ + AddonUserProfileFieldTextareaComponent + ], + entryComponents: [ + AddonUserProfileFieldTextareaComponent + ] +}) +export class AddonUserProfileFieldTextareaModule { + constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldTextareaHandler) { + userProfileFieldDelegate.registerHandler(handler); + } +} \ No newline at end of file diff --git a/src/addon/userprofilefield/userprofilefield.module.ts b/src/addon/userprofilefield/userprofilefield.module.ts new file mode 100644 index 000000000..a946dab74 --- /dev/null +++ b/src/addon/userprofilefield/userprofilefield.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { AddonUserProfileFieldCheckboxModule } from './checkbox/checkbox.module'; +import { AddonUserProfileFieldDatetimeModule } from './datetime/datetime.module'; +import { AddonUserProfileFieldMenuModule } from './menu/menu.module'; +import { AddonUserProfileFieldTextModule } from './text/text.module'; +import { AddonUserProfileFieldTextareaModule } from './textarea/textarea.module'; + + +@NgModule({ + declarations: [], + imports: [ + AddonUserProfileFieldCheckboxModule, + AddonUserProfileFieldDatetimeModule, + AddonUserProfileFieldMenuModule, + AddonUserProfileFieldTextModule, + AddonUserProfileFieldTextareaModule + ], + providers: [ + ], + exports: [] +}) +export class AddonUserProfileFieldModule {} \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2a824241a..c68d1071b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -64,6 +64,8 @@ import { CoreUserModule } from '../core/user/user.module'; // Addon modules. import { AddonCalendarModule } from '../addon/calendar/calendar.module'; +import { AddonUserProfileFieldModule } from '../addon/userprofilefield/userprofilefield.module'; + // For translate loader. AoT requires an exported function for factories. export function createTranslateLoader(http: HttpClient) { @@ -99,7 +101,8 @@ export function createTranslateLoader(http: HttpClient) { CoreSiteHomeModule, CoreContentLinksModule, CoreUserModule, - AddonCalendarModule + AddonCalendarModule, + AddonUserProfileFieldModule ], bootstrap: [IonicApp], entryComponents: [ diff --git a/src/core/user/components/components.module.ts b/src/core/user/components/components.module.ts new file mode 100644 index 000000000..13682bbe7 --- /dev/null +++ b/src/core/user/components/components.module.ts @@ -0,0 +1,36 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { CoreUserProfileFieldComponent } from './user-profile-field/user-profile-field'; + +@NgModule({ + declarations: [ + CoreUserProfileFieldComponent + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + ], + providers: [ + ], + exports: [ + CoreUserProfileFieldComponent + ] +}) +export class CoreUserComponentsModule {} diff --git a/src/core/user/components/user-profile-field/user-profile-field.html b/src/core/user/components/user-profile-field/user-profile-field.html new file mode 100644 index 000000000..825590342 --- /dev/null +++ b/src/core/user/components/user-profile-field/user-profile-field.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/core/user/components/user-profile-field/user-profile-field.scss b/src/core/user/components/user-profile-field/user-profile-field.scss new file mode 100644 index 000000000..a5595aa37 --- /dev/null +++ b/src/core/user/components/user-profile-field/user-profile-field.scss @@ -0,0 +1,5 @@ +core-user-profile-field { + +} + + diff --git a/src/core/user/components/user-profile-field/user-profile-field.ts b/src/core/user/components/user-profile-field/user-profile-field.ts new file mode 100644 index 000000000..1b3dcfee0 --- /dev/null +++ b/src/core/user/components/user-profile-field/user-profile-field.ts @@ -0,0 +1,98 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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, ViewContainerRef, ComponentFactoryResolver, ComponentRef, OnInit } from '@angular/core'; +import { CoreLoggerProvider } from '../../../../providers/logger'; +import { CoreUserProfileFieldDelegate } from '../../providers/user-profile-field-delegate'; + +/** + * Directive to render user profile field. + */ +@Component({ + selector: 'core-user-profile-field', + templateUrl: 'user-profile-field.html' +}) +export class CoreUserProfileFieldComponent implements OnInit { + @Input() field: any; // The profile field to be rendered. + @Input() signup?: boolean = false; // True if editing the field in signup. Defaults to false. + @Input() edit?: boolean = false; // True if editing the field. Defaults to false. + @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + @Input() registerAuth?: string; // Register auth method. E.g. 'email'. + + // Get the containers where to inject dynamic components. We use a setter because they might be inside a *ngIf. + @ViewChild('userProfileField', { read: ViewContainerRef }) set userProfileField (el: ViewContainerRef) { + if (this.field) { + this.createComponent(this.ufDelegate.getComponent(this.field, this.signup, this.registerAuth), el); + } else { + // The component hasn't been initialized yet. Store the container. + this.fieldContainer = el; + } + }; + + protected logger; + + // Instances and containers of all the components that the handler could define. + protected fieldContainer: ViewContainerRef; + protected fieldInstance: any; + + constructor(logger: CoreLoggerProvider, private factoryResolver: ComponentFactoryResolver, + private ufDelegate: CoreUserProfileFieldDelegate) { + this.logger = logger.getInstance('CoreUserProfileFieldComponent'); + } + + /** + * Component being initialized. + */ + ngOnInit() { + this.createComponent(this.ufDelegate.getComponent(this.field, this.signup, this.registerAuth), this.fieldContainer); + } + + /** + * Create a component, add it to a container and set the input data. + * + * @param {any} componentClass The class of the component to create. + * @param {ViewContainerRef} container The container to add the component to. + * @return {boolean} Whether the component was successfully created. + */ + protected createComponent(componentClass: any, container: ViewContainerRef) : boolean { + if (!componentClass || !container) { + // No component to instantiate or container doesn't exist right now. + return false; + } + + if (this.fieldInstance && container === this.fieldContainer) { + // Component already instantiated and the component hasn't been destroyed, nothing to do. + return true; + } + + try { + // Create the component and add it to the container. + const factory = this.factoryResolver.resolveComponentFactory(componentClass), + componentRef = container.createComponent(factory); + + this.fieldContainer = container; + this.fieldInstance = componentRef.instance; + + // Set the Input data. + this.fieldInstance.field = this.field; + this.fieldInstance.edit = this.edit; + this.fieldInstance.model = this.model; + + return true; + } catch(ex) { + this.logger.error('Error creating user field component', ex, componentClass); + return false; + } + } +} diff --git a/src/core/user/pages/about/about.html b/src/core/user/pages/about/about.html index 6556144a4..f95e6f7c3 100644 --- a/src/core/user/pages/about/about.html +++ b/src/core/user/pages/about/about.html @@ -59,6 +59,7 @@

{{ 'core.user.interests' | translate}}

+ {{ 'core.user.description' | translate}} diff --git a/src/core/user/pages/about/about.module.ts b/src/core/user/pages/about/about.module.ts index 07f43dbba..22ea2afdd 100644 --- a/src/core/user/pages/about/about.module.ts +++ b/src/core/user/pages/about/about.module.ts @@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreUserAboutPage } from './about'; import { CoreDirectivesModule } from '../../../../directives/directives.module'; import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreUserComponentsModule } from '../../components/components.module'; @NgModule({ declarations: [ @@ -26,6 +27,7 @@ import { CoreComponentsModule } from '../../../../components/components.module'; imports: [ CoreComponentsModule, CoreDirectivesModule, + CoreUserComponentsModule, IonicPageModule.forChild(CoreUserAboutPage), TranslateModule.forChild() ], diff --git a/src/core/user/pages/profile/profile.ts b/src/core/user/pages/profile/profile.ts index 143c5d850..75f9456a1 100644 --- a/src/core/user/pages/profile/profile.ts +++ b/src/core/user/pages/profile/profile.ts @@ -23,7 +23,7 @@ import { CoreEventsProvider } from '../../../../providers/events'; import { CoreSitesProvider } from '../../../../providers/sites'; import { CoreMimetypeUtilsProvider } from '../../../../providers/utils/mimetype'; import { CoreFileUploaderHelperProvider } from '../../../fileuploader/providers/helper'; -import { CoreUserDelegate } from '../../providers/delegate'; +import { CoreUserDelegate } from '../../providers/user-delegate'; /** * Page that displays an user profile page. @@ -103,7 +103,6 @@ export class CoreUserProfilePage { this.isLoadingHandlers = true; this.userDelegate.getProfileHandlersFor(user, this.courseId).then((handlers) => { - console.error(handlers); this.actionHandlers = []; this.newPageHandlers = []; this.communicationHandlers = []; diff --git a/src/core/user/providers/delegate.ts b/src/core/user/providers/user-delegate.ts similarity index 98% rename from src/core/user/providers/delegate.ts rename to src/core/user/providers/user-delegate.ts index 7bb2f7638..659d09ba8 100644 --- a/src/core/user/providers/delegate.ts +++ b/src/core/user/providers/user-delegate.ts @@ -124,7 +124,7 @@ export class CoreUserDelegate extends CoreDelegate { protected featurePrefix = '$mmUserDelegate_'; constructor(protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, - private coursesProvider: CoreCoursesProvider, protected eventsProvider: CoreEventsProvider) { + private coursesProvider: CoreCoursesProvider, protected eventsProvider: CoreEventsProvider) { super('CoreUserDelegate', loggerProvider, sitesProvider, eventsProvider); } diff --git a/src/core/user/providers/user-handler.ts b/src/core/user/providers/user-handler.ts index 3a37255e1..48f439250 100644 --- a/src/core/user/providers/user-handler.ts +++ b/src/core/user/providers/user-handler.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from './delegate'; +import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from './user-delegate'; import { CoreSitesProvider } from '../../../providers/sites'; /** diff --git a/src/core/user/providers/user-profile-field-delegate.ts b/src/core/user/providers/user-profile-field-delegate.ts new file mode 100644 index 000000000..b3b8784dd --- /dev/null +++ b/src/core/user/providers/user-profile-field-delegate.ts @@ -0,0 +1,164 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { CoreDelegate, CoreDelegateHandler } from '../../../classes/delegate'; +import { CoreLoggerProvider } from '../../../providers/logger'; +import { CoreSitesProvider } from '../../../providers/sites'; +import { CoreEventsProvider } from '../../../providers/events'; + +export interface CoreUserProfileFieldHandler extends CoreDelegateHandler { + + /** + * Return the Component to use to display the user profile field. + * + * @param {any} field User field to get the data for. + * @param {boolean} [signup] True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @return {any} The component to use, undefined if not found. + */ + getComponent(field: any, signup: boolean, registerAuth: string): any; + + /** + * Get the data to send for the field based on the input data. + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @param {any} model Model with the input data. + * @return {Promise|CoreUserProfileFieldHandlerData} Data to send for the field. + */ + getData?(field: any, signup: boolean, registerAuth: string, model: any): + Promise | CoreUserProfileFieldHandlerData; +}; + +export interface CoreUserProfileFieldHandlerData { + /** + * Name to display. + * @type {string} + */ + name: string; + + /** + * Field type. + * @type {string} + */ + type?: string; + + /** + * Value of the field. + * @type {any} + */ + value: any; +}; + +/** + * Service to interact with user profile fields. Provides functions to register a plugin. + */ +@Injectable() +export class CoreUserProfileFieldDelegate extends CoreDelegate { + protected handlers: {[s: string]: CoreUserProfileFieldHandler} = {}; + protected enabledHandlers: {[s: string]: CoreUserProfileFieldHandler} = {}; + + constructor(protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, + protected eventsProvider: CoreEventsProvider) { + super('CoreUserProfileFieldDelegate', loggerProvider, sitesProvider, eventsProvider); + } + + /** + * Get the component to use to display an user field. + * + * @param {any} field User field to get the directive for. + * @param {boolean} signup True if user is in signup page. + * @param {string} registerAuth Register auth method. E.g. 'email' + * @return {any} The component to use, undefined if not found. + */ + getComponent(field: any, signup: boolean, registerAuth: string) : any { + let type = field.type || field.datatype; + return this.executeFunction(type, 'getComponent', [field, signup, registerAuth]); + } + + /** + * Get the data to send for a certain field based on the input data. + * + * @param {any} field User field to get the data for. + * @param {boolean} signup True if user is in signup page. + * @param {string} registerAuth Register auth method. E.g. 'email'. + * @param {any} model Model with the input data. + * @return {Promise} Data to send for the field. + */ + getDataForField(field: any, signup: boolean, registerAuth: string, model: any): Promise { + let handler = this.getHandler(field, signup); + + if (handler) { + let name = 'profile_field_' + field.shortname; + if (handler.getData) { + return Promise.resolve(handler.getData(field, signup, registerAuth, model)); + } else if (field.shortname && typeof model[name] != 'undefined') { + // Handler doesn't implement the function, but the model has data for the field. + return Promise.resolve({ + type: field.type || field.datatype, + name: name, + value: model[name] + }); + } + } + return Promise.reject(null); + } + + /** + * Get the data to send for a list of fields based on the input data. + * + * @param {any[]} fields User fields to get the data for. + * @param {boolean} [signup] True if user is in signup page. + * @param {string} [registerAuth] Register auth method. E.g. 'email'. + * @param {any} model Model with the input data. + * @return {Promise} Data to send. + */ + getDataForFields(fields: any[], signup = false, registerAuth = "", model: any): Promise { + let result = [], + promises = []; + + fields.forEach((field) => { + this.getDataForField(field, signup, registerAuth, model).then((data) => { + result.push(data); + }).catch(() => { + // Ignore errors. + }); + }); + + return Promise.all(promises).then(() => { + return result; + }); + } + + /** + * Get a handler. + * + * @param {any} field User field to get the directive for. + * @param {boolean} signup True if user is in signup page. + * @return {any} Handler. + */ + protected getHandler(field: any, signup: boolean): any { + let type = field.type || field.datatype; + + if (signup) { + if (this.handlers[type]) { + return this.handlers[type]; + } + return false; + } + + return this.enabledHandlers[type]; + } +} diff --git a/src/core/user/user.module.ts b/src/core/user/user.module.ts index 3f052aa1d..474d9171a 100644 --- a/src/core/user/user.module.ts +++ b/src/core/user/user.module.ts @@ -13,7 +13,8 @@ // limitations under the License. import { NgModule } from '@angular/core'; -import { CoreUserDelegate } from './providers/delegate'; +import { CoreUserDelegate } from './providers/user-delegate'; +import { CoreUserProfileFieldDelegate } from './providers/user-profile-field-delegate'; import { CoreUserProvider } from './providers/user'; import { CoreUserHelperProvider } from './providers/helper'; import { CoreUserProfileMailHandler } from './providers/user-handler'; @@ -25,6 +26,7 @@ import { CoreUserProfileMailHandler } from './providers/user-handler'; ], providers: [ CoreUserDelegate, + CoreUserProfileFieldDelegate, CoreUserProfileMailHandler, CoreUserProvider, CoreUserHelperProvider From 61a477afd516bbf72b4221fb08b8f5cbc9230bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 18 Jan 2018 16:38:11 +0100 Subject: [PATCH 06/13] MOBILE-2317 rte: Initial implementation --- src/components/components.module.ts | 7 +- .../mark-required/mark-required.scss | 14 +- .../rich-text-editor/rich-text-editor.html | 29 +++ .../rich-text-editor/rich-text-editor.scss | 71 +++++++ .../rich-text-editor/rich-text-editor.ts | 194 ++++++++++++++++++ 5 files changed, 305 insertions(+), 10 deletions(-) create mode 100644 src/components/rich-text-editor/rich-text-editor.html create mode 100644 src/components/rich-text-editor/rich-text-editor.scss create mode 100644 src/components/rich-text-editor/rich-text-editor.ts diff --git a/src/components/components.module.ts b/src/components/components.module.ts index a9b03da57..7d62c20f4 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -36,6 +36,7 @@ import { CoreLocalFileComponent } from './local-file/local-file'; import { CoreSitePickerComponent } from './site-picker/site-picker'; import { CoreTabsComponent } from './tabs/tabs'; import { CoreTabComponent } from './tabs/tab'; +import { CoreRichTextEditorComponent } from './rich-text-editor/rich-text-editor'; @NgModule({ declarations: [ @@ -57,7 +58,8 @@ import { CoreTabComponent } from './tabs/tab'; CoreLocalFileComponent, CoreSitePickerComponent, CoreTabsComponent, - CoreTabComponent + CoreTabComponent, + CoreRichTextEditorComponent ], entryComponents: [ CoreContextMenuPopoverComponent, @@ -86,7 +88,8 @@ import { CoreTabComponent } from './tabs/tab'; CoreLocalFileComponent, CoreSitePickerComponent, CoreTabsComponent, - CoreTabComponent + CoreTabComponent, + CoreRichTextEditorComponent ] }) export class CoreComponentsModule {} diff --git a/src/components/mark-required/mark-required.scss b/src/components/mark-required/mark-required.scss index deacc173b..1e6bebc83 100644 --- a/src/components/mark-required/mark-required.scss +++ b/src/components/mark-required/mark-required.scss @@ -1,9 +1,7 @@ -*[core-mark-required] { - .core-input-required-asterisk, .icon.core-input-required-asterisk { - color: $red !important; - font-size: 8px; - padding-left: 4px; - line-height: 100%; - vertical-align: top; - } +.core-input-required-asterisk, .icon.core-input-required-asterisk { + color: $red !important; + font-size: 8px; + padding-left: 4px; + line-height: 100%; + vertical-align: top; } diff --git a/src/components/rich-text-editor/rich-text-editor.html b/src/components/rich-text-editor/rich-text-editor.html new file mode 100644 index 000000000..72eee6e14 --- /dev/null +++ b/src/components/rich-text-editor/rich-text-editor.html @@ -0,0 +1,29 @@ +
+
+
+ + +
+ + + + + + + + + + + + +
+
+ +
+ +
+ +
+
+ + diff --git a/src/components/rich-text-editor/rich-text-editor.scss b/src/components/rich-text-editor/rich-text-editor.scss new file mode 100644 index 000000000..096779bb8 --- /dev/null +++ b/src/components/rich-text-editor/rich-text-editor.scss @@ -0,0 +1,71 @@ +core-rich-text-editor { + height: 40vh; + overflow: hidden; + min-height: 30vh; + + > div { + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + } + + .core-rte-editor, .core-textarea { + padding: 2px; + margin: 2px; + width: 100%; + resize: none; + background-color: $white; + flex-grow: 1; + * { + overflow: hidden; + } + } + + .core-rte-editor { + -webkit-user-select: auto !important; + word-wrap: break-word; + overflow-x: hidden; + overflow-y: auto; + cursor: text; + img { + padding-left: 2px; + max-width: 95%; + } + &:empty:before { + content: attr(data-placeholder-text); + display: block; + color: $gray-light; + font-weight: bold; + } + } + + .core-textarea textarea { + margin: 0 !important; + padding: 0; + height: 100% !important; + width: 100% !important; + resize: none; + overflow-x: hidden; + overflow-y: auto; + } + + div.formatOptions { + background: $gray-dark; + margin: 5px 1px 15px 1px; + text-align: center; + flex-grow: 0; + width: 100%; + z-index: 1; + button { + background: $gray-dark; + color: $white; + font-size: 1.1em; + height: 35px; + min-width: 30px; + padding-left: 1px; + padding-right: 1px; + } + } + +} \ No newline at end of file diff --git a/src/components/rich-text-editor/rich-text-editor.ts b/src/components/rich-text-editor/rich-text-editor.ts new file mode 100644 index 000000000..3f41f9f49 --- /dev/null +++ b/src/components/rich-text-editor/rich-text-editor.ts @@ -0,0 +1,194 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core'; +import { TextInput } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '../../providers/utils/dom'; +import { FormControl } from '@angular/forms'; +import { Keyboard } from '@ionic-native/keyboard'; + +/** + * Directive to display a rich text editor if enabled. + * +* If enabled, this directive will show a rich text editor. Otherwise it'll show a regular textarea. + * + * This directive requires an OBJECT model. The text written in the editor or textarea will be stored inside + * a "text" property in that object. This is to ensure 2-way data-binding, since using a string as a model + * could be easily broken. + * + * Example: + * + * + * + * In the example above, the text written in the editor will be stored in newpost.text. + */ +@Component({ + selector: 'core-rich-text-editor', + templateUrl: 'rich-text-editor.html' +}) +export class CoreRichTextEditorComponent { + // Based on: https://github.com/judgewest2000/Ionic3RichText/ + // @todo: Resize, images, anchor button, fullscreen... + + @Input() placeholder?: string = ""; // Placeholder to set in textarea. + @Input() control: FormControl; // Form control. + @Output() public contentChanged: EventEmitter; + + @ViewChild('editor') editor: ElementRef; // WYSIWYG editor. + @ViewChild('textarea') textarea: TextInput; // Textarea editor. + @ViewChild('decorate') decorate: ElementRef; // Buttons. + + rteEnabled: boolean = false; + uniqueId = `rte{Math.floor(Math.random() * 1000000)}`; + editorElement: HTMLDivElement; + + constructor(private domUtils: CoreDomUtilsProvider, private keyboard: Keyboard) { + this.contentChanged = new EventEmitter(); + } + + /** + * Init editor + */ + ngAfterContentInit() { + this.domUtils.isRichTextEditorEnabled().then((enabled) => { + this.rteEnabled = !!enabled; + }); + + // Setup the editor. + this.editorElement = this.editor.nativeElement as HTMLDivElement; + this.editorElement.innerHTML = this.control.value; + this.textarea.value = this.control.value; + this.control.setValue(this.control.value); + + this.editorElement.onchange = this.onChange.bind(this); + this.editorElement.onkeyup = this.onChange.bind(this); + this.editorElement.onpaste = this.onChange.bind(this); + this.editorElement.oninput = this.onChange.bind(this); + + // Setup button actions. + let buttons = (this.decorate.nativeElement as HTMLDivElement).getElementsByTagName('button'); + for (let i = 0; i < buttons.length; i++) { + let button = buttons[i], + command = button.getAttribute('data-command'); + + if (command) { + if (command.includes('|')) { + let parameter = command.split('|')[1]; + command = command.split('|')[0]; + + button.addEventListener('click', ($event) => { + this.buttonAction($event, command, parameter); + }); + } else { + button.addEventListener('click', ($event) => { + this.buttonAction($event, command); + }); + } + } + } + } + + /** + * On change function to sync with form data. + */ + onChange($event) { + if (this.rteEnabled) { + if (this.isNullOrWhiteSpace(this.editorElement.innerText)) { + this.clearText(); + } else { + this.control.setValue(this.editorElement.innerHTML); + } + } else { + if (this.isNullOrWhiteSpace(this.textarea.value)) { + this.clearText(); + } else { + this.control.setValue(this.textarea.value); + } + } + this.contentChanged.emit(this.control.value); + } + + /** + * Toggle from rte editor to textarea syncing values. + */ + toggleEditor($event) { + $event.preventDefault(); + $event.stopPropagation(); + + if (this.isNullOrWhiteSpace(this.control.value)) { + this.clearText(); + } else { + this.editorElement.innerHTML = this.control.value; + this.textarea.value = this.control.value; + } + + this.rteEnabled = !this.rteEnabled; + + // Set focus and cursor at the end. + setTimeout(() => { + if (this.rteEnabled) { + this.editorElement.focus(); + let range = document.createRange(); + range.selectNodeContents(this.editorElement); + range.collapse(false); + let sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } else { + this.textarea.setFocus(); + } + setTimeout(() => { + this.keyboard.show(); + }, 1); + }, 1); + } + + /** + * Check if text is empty. + * @param {string} value text + */ + private isNullOrWhiteSpace(value: string) { + if (value == null || typeof value == "undefined") { + return true; + } + value = value.replace(/[\n\r]/g, ''); + value = value.split(' ').join(''); + + return value.length === 0; + } + + /** + * Clear the text. + */ + clearText() { + this.editorElement.innerHTML = '

'; + this.textarea.value = ''; + this.control.setValue(null); + } + + /** + * Execute an action over the selected text. + * API docs: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand + * + * @param {any} $event Event data + * @param {string} command Command to execute. + * @param {any} [parameters] Parameters of the command. + */ + private buttonAction($event: any, command: string, parameters: any = null) { + $event.preventDefault(); + $event.stopPropagation(); + document.execCommand(command, false, parameters); + } +} \ No newline at end of file From d7c6c25292b8de809017d08c1206e22c1fb3a26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 18 Jan 2018 16:38:41 +0100 Subject: [PATCH 07/13] MOBILE-2317 login: Add custom user profile field to sign-up --- .../checkbox/component/checkbox.html | 5 +- .../checkbox/component/checkbox.scss | 0 .../checkbox/component/checkbox.ts | 18 +++-- .../checkbox/providers/handler.ts | 13 ++-- .../datetime/component/datetime.html | 6 +- .../datetime/component/datetime.scss | 0 .../datetime/component/datetime.ts | 22 ++++-- .../datetime/providers/handler.ts | 30 ++------ .../userprofilefield/menu/component/menu.html | 6 +- .../userprofilefield/menu/component/menu.scss | 0 .../userprofilefield/menu/component/menu.ts | 18 +++-- .../menu/providers/handler.ts | 13 ++-- .../userprofilefield/text/component/text.html | 6 +- .../userprofilefield/text/component/text.scss | 0 .../userprofilefield/text/component/text.ts | 18 +++-- .../text/providers/handler.ts | 11 ++- .../textarea/component/textarea.html | 8 +-- .../textarea/component/textarea.scss | 0 .../textarea/component/textarea.ts | 26 ++++--- .../textarea/providers/handler.ts | 42 +++++------- src/classes/delegate.ts | 46 +++++++++++-- src/core/login/login.module.ts | 2 +- .../pages/email-signup/email-signup.html | 20 +++--- .../pages/email-signup/email-signup.module.ts | 2 + .../login/pages/email-signup/email-signup.ts | 33 ++++----- src/core/login/providers/helper.ts | 10 +-- .../user-profile-field/user-profile-field.ts | 17 +++-- src/core/user/lang/en.json | 3 + src/core/user/providers/helper.ts | 1 - .../providers/user-profile-field-delegate.ts | 68 +++++++------------ src/providers/utils/dom.ts | 14 ++++ src/providers/utils/time.ts | 8 +++ 32 files changed, 253 insertions(+), 213 deletions(-) delete mode 100644 src/addon/userprofilefield/checkbox/component/checkbox.scss delete mode 100644 src/addon/userprofilefield/datetime/component/datetime.scss delete mode 100644 src/addon/userprofilefield/menu/component/menu.scss delete mode 100644 src/addon/userprofilefield/text/component/text.scss delete mode 100644 src/addon/userprofilefield/textarea/component/textarea.scss diff --git a/src/addon/userprofilefield/checkbox/component/checkbox.html b/src/addon/userprofilefield/checkbox/component/checkbox.html index 8ab6fe45f..41170009e 100644 --- a/src/addon/userprofilefield/checkbox/component/checkbox.html +++ b/src/addon/userprofilefield/checkbox/component/checkbox.html @@ -9,8 +9,9 @@

- + {{ field.name }} - + + \ No newline at end of file diff --git a/src/addon/userprofilefield/checkbox/component/checkbox.scss b/src/addon/userprofilefield/checkbox/component/checkbox.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/addon/userprofilefield/checkbox/component/checkbox.ts b/src/addon/userprofilefield/checkbox/component/checkbox.ts index 28f11a4bf..b909bfac7 100644 --- a/src/addon/userprofilefield/checkbox/component/checkbox.ts +++ b/src/addon/userprofilefield/checkbox/component/checkbox.ts @@ -13,20 +13,22 @@ // limitations under the License. import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; /** * Directive to render a checkbox user profile field. */ @Component({ - selector: 'core-user-profile-field-checkbox', + selector: 'addon-user-profile-field-checkbox', templateUrl: 'checkbox.html' }) export class AddonUserProfileFieldCheckboxComponent implements OnInit { @Input() field: any; // The profile field to be rendered. @Input() edit?: boolean = false; // True if editing the field. Defaults to false. - @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + @Input() disabled?: boolean = false; // True if disabled. Defaults to false. + @Input() form?: FormGroup; // Form where to add the form control. - constructor() {} + constructor(private fb: FormBuilder) {} /** * Component being initialized. @@ -34,13 +36,15 @@ export class AddonUserProfileFieldCheckboxComponent implements OnInit { ngOnInit() { let field = this.field; - if (field && this.edit && this.model) { + if (field && this.edit && this.form) { field.modelName = 'profile_field_' + field.shortname; // Initialize the value. - if (typeof field.defaultdata != 'undefined' && typeof this.model[field.modelName] == 'undefined') { - this.model[field.modelName] = field.defaultdata && field.defaultdata !== '0' && field.defaultdata !== 'false'; - } + let formData = { + value: field.defaultdata && field.defaultdata !== '0' && field.defaultdata !== 'false', + disabled: this.disabled + }; + this.form.addControl(field.modelName, this.fb.control(formData, field.required && !field.locked ? Validators.requiredTrue : null)); } } diff --git a/src/addon/userprofilefield/checkbox/providers/handler.ts b/src/addon/userprofilefield/checkbox/providers/handler.ts index 69c7ac051..ee5147f99 100644 --- a/src/addon/userprofilefield/checkbox/providers/handler.ts +++ b/src/addon/userprofilefield/checkbox/providers/handler.ts @@ -40,17 +40,17 @@ export class AddonUserProfileFieldCheckboxHandler implements CoreUserProfileFiel * @param {any} field User field to get the data for. * @param {boolean} signup True if user is in signup page. * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} model Model with the input data. + * @param {any} formValues Form Values. * @return {CoreUserProfileFieldHandlerData} Data to send for the field. */ - getData(field: any, signup: boolean, registerAuth: string, model: any): CoreUserProfileFieldHandlerData { + getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { let name = 'profile_field_' + field.shortname; - if (typeof model[name] != 'undefined') { + if (typeof formValues[name] != 'undefined') { return { type: 'checkbox', name: name, - value: model[name] ? 1 : 0 + value: formValues[name] ? 1 : 0 }; } } @@ -58,12 +58,9 @@ export class AddonUserProfileFieldCheckboxHandler implements CoreUserProfileFiel /** * Return the Component to use to display the user profile field. * - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. * @return {any} The component to use, undefined if not found. */ - getComponent(field: any, signup: boolean, registerAuth: string) { + getComponent() { return AddonUserProfileFieldCheckboxComponent; } diff --git a/src/addon/userprofilefield/datetime/component/datetime.html b/src/addon/userprofilefield/datetime/component/datetime.html index cd19d4595..57b8e7ca2 100644 --- a/src/addon/userprofilefield/datetime/component/datetime.html +++ b/src/addon/userprofilefield/datetime/component/datetime.html @@ -4,7 +4,7 @@

{{ field.value * 1000 | coreFormatDate:"dfmediumdate"}}

- - {{ field.name }} - + + {{ field.name }} + \ No newline at end of file diff --git a/src/addon/userprofilefield/datetime/component/datetime.scss b/src/addon/userprofilefield/datetime/component/datetime.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/addon/userprofilefield/datetime/component/datetime.ts b/src/addon/userprofilefield/datetime/component/datetime.ts index 4526abc5e..737b6bed7 100644 --- a/src/addon/userprofilefield/datetime/component/datetime.ts +++ b/src/addon/userprofilefield/datetime/component/datetime.ts @@ -13,21 +13,23 @@ // limitations under the License. import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { CoreTimeUtilsProvider } from '../../../../providers/utils/time'; /** * Directive to render a datetime user profile field. */ @Component({ - selector: 'core-user-profile-field-datetime', + selector: 'addon-user-profile-field-datetime', templateUrl: 'datetime.html' }) export class AddonUserProfileFieldDatetimeComponent implements OnInit { @Input() field: any; // The profile field to be rendered. @Input() edit?: boolean = false; // True if editing the field. Defaults to false. - @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + @Input() disabled?: boolean = false; // True if disabled. Defaults to false. + @Input() form?: FormGroup; // Form where to add the form control. - - constructor() {} + constructor(private fb: FormBuilder, private timeUtils: CoreTimeUtilsProvider) {} /** * Component being initialized. @@ -35,12 +37,12 @@ export class AddonUserProfileFieldDatetimeComponent implements OnInit { ngOnInit() { let field = this.field, year; - if (field && this.edit && this.model) { + if (field && this.edit && this.form) { field.modelName = 'profile_field_' + field.shortname; // Check if it's only date or it has time too. - field.hasTime = field.param3 && field.param3 !== '0' && field.param3 !== 'false'; - field.format = field.hasTime ? 'core.dffulldate' : 'core.dfdaymonthyear'; + let hasTime = field.param3 && field.param3 !== '0' && field.param3 !== 'false'; + field.format = hasTime ? this.timeUtils.getLocalizedDateFormat('LLL') : this.timeUtils.getLocalizedDateFormat('LL'); // Check min value. if (field.param1) { @@ -57,6 +59,12 @@ export class AddonUserProfileFieldDatetimeComponent implements OnInit { field.max = year; } } + + let formData = { + value: field.defaultdata, + disabled: this.disabled + }; + this.form.addControl(field.modelName, this.fb.control(formData, field.required && !field.locked ? Validators.required : null)); } } diff --git a/src/addon/userprofilefield/datetime/providers/handler.ts b/src/addon/userprofilefield/datetime/providers/handler.ts index ad3c95414..16e1f7048 100644 --- a/src/addon/userprofilefield/datetime/providers/handler.ts +++ b/src/addon/userprofilefield/datetime/providers/handler.ts @@ -41,31 +41,18 @@ export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFiel * @param {any} field User field to get the data for. * @param {boolean} signup True if user is in signup page. * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} model Model with the input data. + * @param {any} formValues Form Values. * @return {CoreUserProfileFieldHandlerData} Data to send for the field. */ - getData(field: any, signup: boolean, registerAuth: string, model: any): CoreUserProfileFieldHandlerData { - let hasTime = field.param3 && field.param3 !== '0' && field.param3 !== 'false', - modelName = 'profile_field_' + field.shortname, - date = JSON.parse(JSON.stringify(model[modelName + '_date'])), - time; - - if (date) { - if (hasTime && this.platform.is('ios')) { - // In iOS the time is in a different input. Add it to the date. - time = model[modelName + '_time']; - if (!time) { - return; - } - - date.setHours(time.getHours()); - date.setMinutes(time.getMinutes()); - } + getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { + let name = 'profile_field_' + field.shortname; + if (formValues[name]) { + let milliseconds = new Date(formValues[name]).getTime(); return { type: 'datetime', name: 'profile_field_' + field.shortname, - value: Math.round(date.getTime() / 1000) + value: Math.round(milliseconds / 1000) }; } } @@ -73,12 +60,9 @@ export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFiel /** * Return the Component to use to display the user profile field. * - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. * @return {any} The component to use, undefined if not found. */ - getComponent(field: any, signup: boolean, registerAuth: string) { + getComponent() { return AddonUserProfileFieldDatetimeComponent; } diff --git a/src/addon/userprofilefield/menu/component/menu.html b/src/addon/userprofilefield/menu/component/menu.html index 23ee49f9b..68198ddc9 100644 --- a/src/addon/userprofilefield/menu/component/menu.html +++ b/src/addon/userprofilefield/menu/component/menu.html @@ -4,9 +4,9 @@

- - {{ field.name }} - + + {{ field.name }} + {{ 'core.choosedots' | translate }} {{option}} diff --git a/src/addon/userprofilefield/menu/component/menu.scss b/src/addon/userprofilefield/menu/component/menu.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/addon/userprofilefield/menu/component/menu.ts b/src/addon/userprofilefield/menu/component/menu.ts index 98bf2f43b..a7048af52 100644 --- a/src/addon/userprofilefield/menu/component/menu.ts +++ b/src/addon/userprofilefield/menu/component/menu.ts @@ -13,20 +13,22 @@ // limitations under the License. import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; /** * Directive to render a menu user profile field. */ @Component({ - selector: 'core-user-profile-field-menu', + selector: 'addon-user-profile-field-menu', templateUrl: 'menu.html' }) export class AddonUserProfileFieldMenuComponent implements OnInit { @Input() field: any; // The profile field to be rendered. @Input() edit?: boolean = false; // True if editing the field. Defaults to false. - @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + @Input() disabled?: boolean = false; // True if disabled. Defaults to false. + @Input() form?: FormGroup; // Form where to add the form control. - constructor() {} + constructor(private fb: FormBuilder) {} /** * Component being initialized. @@ -34,7 +36,7 @@ export class AddonUserProfileFieldMenuComponent implements OnInit { ngOnInit() { let field = this.field; - if (field && this.edit && this.model) { + if (field && this.edit && this.form) { field.modelName = 'profile_field_' + field.shortname; // Parse options. @@ -44,10 +46,12 @@ export class AddonUserProfileFieldMenuComponent implements OnInit { field.options = []; } + let formData = { + value: field.defaultdata, + disabled: this.disabled + }; // Initialize the value using default data. - if (typeof field.defaultdata != 'undefined' && typeof this.model[field.modelName] == 'undefined') { - this.model[field.modelName] = field.defaultdata; - } + this.form.addControl(field.modelName, this.fb.control(formData, field.required && !field.locked ? Validators.required : null)); } } diff --git a/src/addon/userprofilefield/menu/providers/handler.ts b/src/addon/userprofilefield/menu/providers/handler.ts index 470fd1d3d..7a04a2b61 100644 --- a/src/addon/userprofilefield/menu/providers/handler.ts +++ b/src/addon/userprofilefield/menu/providers/handler.ts @@ -40,17 +40,17 @@ export class AddonUserProfileFieldMenuHandler implements CoreUserProfileFieldHan * @param {any} field User field to get the data for. * @param {boolean} signup True if user is in signup page. * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} model Model with the input data. + * @param {any} formValues Form Values. * @return {CoreUserProfileFieldHandlerData} Data to send for the field. */ - getData(field: any, signup: boolean, registerAuth: string, model: any): CoreUserProfileFieldHandlerData { + getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { let name = 'profile_field_' + field.shortname; - if (model[name]) { + if (formValues[name]) { return { type: 'menu', name: name, - value: model[name] + value: formValues[name] }; } } @@ -58,12 +58,9 @@ export class AddonUserProfileFieldMenuHandler implements CoreUserProfileFieldHan /** * Return the Component to use to display the user profile field. * - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. * @return {any} The component to use, undefined if not found. */ - getComponent(field: any, signup: boolean, registerAuth: string) { + getComponent() { return AddonUserProfileFieldMenuComponent; } diff --git a/src/addon/userprofilefield/text/component/text.html b/src/addon/userprofilefield/text/component/text.html index c27315320..aaa43b1ac 100644 --- a/src/addon/userprofilefield/text/component/text.html +++ b/src/addon/userprofilefield/text/component/text.html @@ -4,7 +4,7 @@

- - {{ field.name }} - + + {{ field.name }} + diff --git a/src/addon/userprofilefield/text/component/text.scss b/src/addon/userprofilefield/text/component/text.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/addon/userprofilefield/text/component/text.ts b/src/addon/userprofilefield/text/component/text.ts index f8fd5b72e..3efb230e5 100644 --- a/src/addon/userprofilefield/text/component/text.ts +++ b/src/addon/userprofilefield/text/component/text.ts @@ -13,20 +13,22 @@ // limitations under the License. import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; /** * Directive to render a text user profile field. */ @Component({ - selector: 'core-user-profile-field-text', + selector: 'addon-user-profile-field-text', templateUrl: 'text.html' }) export class AddonUserProfileFieldTextComponent implements OnInit { @Input() field: any; // The profile field to be rendered. @Input() edit?: boolean = false; // True if editing the field. Defaults to false. - @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + @Input() disabled?: boolean = false; // True if disabled. Defaults to false. + @Input() form?: FormGroup; // Form where to add the form control. - constructor() {} + constructor(private fb: FormBuilder) {} /** * Component being initialized. @@ -34,7 +36,7 @@ export class AddonUserProfileFieldTextComponent implements OnInit { ngOnInit() { let field = this.field; - if (field && this.edit && this.model) { + if (field && this.edit && this.form) { field.modelName = 'profile_field_' + field.shortname; // Check max length. @@ -45,10 +47,12 @@ export class AddonUserProfileFieldTextComponent implements OnInit { // Check if it's a password or text. field.inputType = field.param3 && field.param3 !== '0' && field.param3 !== 'false' ? 'password' : 'text'; + let formData = { + value: field.defaultdata, + disabled: this.disabled + }; // Initialize the value using default data. - if (typeof field.defaultdata != 'undefined' && typeof this.model[field.modelName] == 'undefined') { - this.model[field.modelName] = field.defaultdata; - } + this.form.addControl(field.modelName, this.fb.control(formData, field.required && !field.locked ? Validators.required : null)); } } diff --git a/src/addon/userprofilefield/text/providers/handler.ts b/src/addon/userprofilefield/text/providers/handler.ts index 30949f452..cd6754e70 100644 --- a/src/addon/userprofilefield/text/providers/handler.ts +++ b/src/addon/userprofilefield/text/providers/handler.ts @@ -41,28 +41,25 @@ export class AddonUserProfileFieldTextHandler implements CoreUserProfileFieldHan * @param {any} field User field to get the data for. * @param {boolean} signup True if user is in signup page. * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} model Model with the input data. + * @param {any} formValues Form Values. * @return {CoreUserProfileFieldHandlerData} Data to send for the field. */ - getData(field: any, signup: boolean, registerAuth: string, model: any): CoreUserProfileFieldHandlerData { + getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { let name = 'profile_field_' + field.shortname; return { type: 'text', name: name, - value: this.textUtils.cleanTags(model[name]) + value: this.textUtils.cleanTags(formValues[name]) }; } /** * Return the Component to use to display the user profile field. * - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. * @return {any} The component to use, undefined if not found. */ - getComponent(field: any, signup: boolean, registerAuth: string) { + getComponent() { return AddonUserProfileFieldTextComponent; } diff --git a/src/addon/userprofilefield/textarea/component/textarea.html b/src/addon/userprofilefield/textarea/component/textarea.html index 003d70ef2..4921c3675 100644 --- a/src/addon/userprofilefield/textarea/component/textarea.html +++ b/src/addon/userprofilefield/textarea/component/textarea.html @@ -4,7 +4,7 @@

- - {{ field.name }} - - + + {{ field.name }} + + \ No newline at end of file diff --git a/src/addon/userprofilefield/textarea/component/textarea.scss b/src/addon/userprofilefield/textarea/component/textarea.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/addon/userprofilefield/textarea/component/textarea.ts b/src/addon/userprofilefield/textarea/component/textarea.ts index 967c4081c..dbcf50dfc 100644 --- a/src/addon/userprofilefield/textarea/component/textarea.ts +++ b/src/addon/userprofilefield/textarea/component/textarea.ts @@ -13,18 +13,22 @@ // limitations under the License. import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup, Validators, FormControl} from '@angular/forms'; /** * Directive to render a textarea user profile field. */ @Component({ - selector: 'core-user-profile-field-textarea', + selector: 'addon-user-profile-field-textarea', templateUrl: 'textarea.html' }) export class AddonUserProfileFieldTextareaComponent implements OnInit { @Input() field: any; // The profile field to be rendered. @Input() edit?: boolean = false; // True if editing the field. Defaults to false. - @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + @Input() disabled?: boolean = false; // True if disabled. Defaults to false. + @Input() form?: FormGroup; // Form where to add the form control. + + control: FormControl constructor() {} @@ -34,17 +38,17 @@ export class AddonUserProfileFieldTextareaComponent implements OnInit { ngOnInit() { let field = this.field; - if (field && this.edit && this.model) { + if (field && this.edit && this.form) { field.modelName = 'profile_field_' + field.shortname; - this.model[field.modelName] = { - format: 1 - }; - - // Initialize the value using default data. - if (typeof field.defaultdata != 'undefined' && typeof this.model[field.modelName].text == 'undefined') { - this.model[field.modelName].text = field.defaultdata; - } } + + let formData = { + value: field.defaultdata, + disabled: this.disabled + }; + + this.control = new FormControl(formData, field.required && !field.locked ? Validators.required : null); + this.form.addControl(field.modelName, this.control); } } \ No newline at end of file diff --git a/src/addon/userprofilefield/textarea/providers/handler.ts b/src/addon/userprofilefield/textarea/providers/handler.ts index 544c2ad3e..9604e9a6e 100644 --- a/src/addon/userprofilefield/textarea/providers/handler.ts +++ b/src/addon/userprofilefield/textarea/providers/handler.ts @@ -16,7 +16,6 @@ import { Injectable } from '@angular/core'; import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '../../../../core/user/providers/user-profile-field-delegate'; import { AddonUserProfileFieldTextareaComponent } from '../component/textarea'; import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; -import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; /** * Textarea user profile field handlers. @@ -25,7 +24,7 @@ import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; export class AddonUserProfileFieldTextareaHandler implements CoreUserProfileFieldHandler { name = 'textarea'; - constructor(private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider) {} + constructor(private textUtils: CoreTextUtilsProvider) {} /** * Whether or not the handler is enabled on a site level. @@ -42,41 +41,34 @@ export class AddonUserProfileFieldTextareaHandler implements CoreUserProfileFiel * @param {any} field User field to get the data for. * @param {boolean} signup True if user is in signup page. * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} model Model with the input data. - * @return {Promise} Data to send for the field. + * @param {any} formValues Form Values. + * @return {CoreUserProfileFieldHandlerData} Data to send for the field. */ - getData(field: any, signup: boolean, registerAuth: string, model: any): Promise { + getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { let name = 'profile_field_' + field.shortname; - if (model[name]) { - return this.domUtils.isRichTextEditorEnabled().then((enabled) => { - let text = model[name].text || ''; - if (!enabled) { - // Rich text editor not enabled, add some HTML to the message if needed. - text = this.textUtils.formatHtmlLines(text); - } + if (formValues[name]) { + let text = formValues[name] || ''; + // Add some HTML to the message in case the user edited with textarea. + text = this.textUtils.formatHtmlLines(text); - return { - type: 'textarea', - name: name, - value: JSON.stringify({ - text: text, - format: model[name].format || 1 - }) - }; - }); + return { + type: 'textarea', + name: name, + value: JSON.stringify({ + text: text, + format: 1 // Always send this format. + }) + }; } } /** * Return the Component to use to display the user profile field. * - * @param {any} field User field to get the data for. - * @param {boolean} signup True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. * @return {any} The component to use, undefined if not found. */ - getComponent(field: any, signup: boolean, registerAuth: string) { + getComponent() { return AddonUserProfileFieldTextareaComponent; } diff --git a/src/classes/delegate.ts b/src/classes/delegate.ts index 3185bf435..d9d472fd2 100644 --- a/src/classes/delegate.ts +++ b/src/classes/delegate.ts @@ -92,6 +92,19 @@ export class CoreDelegate { eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this)); } + /** + * Execute a certain function in a enabled handler. + * If the handler isn't found or function isn't defined, call the same function in the default handler. + * + * @param {string} handlerName The handler name. + * @param {string} fnName Name of the function to execute. + * @param {any[]} params Parameters to pass to the function. + * @return {any} Function returned value or default value. + */ + protected executeFunctionOnEnabled(handlerName: string, fnName: string, params?: any[]) : any { + return this.execute(this.enabledHandlers[handlerName], fnName, params); + } + /** * Execute a certain function in a handler. * If the handler isn't found or function isn't defined, call the same function in the default handler. @@ -102,7 +115,19 @@ export class CoreDelegate { * @return {any} Function returned value or default value. */ protected executeFunction(handlerName: string, fnName: string, params?: any[]) : any { - let handler = this.enabledHandlers[handlerName]; + return this.execute(this.handlers[handlerName], fnName, params); + } + + /** + * Execute a certain function in a handler. + * If the handler isn't found or function isn't defined, call the same function in the default handler. + * + * @param {any} handler The handler. + * @param {string} fnName Name of the function to execute. + * @param {any[]} params Parameters to pass to the function. + * @return {any} Function returned value or default value. + */ + private execute(handler: any, fnName: string, params?: any[]) : any { if (handler && handler[fnName]) { return handler[fnName].apply(handler, params); } else if (this.defaultHandler && this.defaultHandler[fnName]) { @@ -110,14 +135,26 @@ export class CoreDelegate { } } + /** + * Get a handler. + * + * @param {string} handlerName The handler name. + * @param {boolean} [enabled] Only enabled, or any. + * @return {any} Handler. + */ + protected getHandler(handlerName: string, enabled = false): any { + return enabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName]; + } + /** * Check if a handler name has a registered handler (not necessarily enabled). * - * @param {string} name The name of the handler. + * @param {string} name The handler name. + * @param {boolean} [enabled] Only enabled, or any. * @return {boolean} If the controller is installed or not. */ - hasHandler(name: string) : boolean { - return typeof this.handlers[name] !== 'undefined'; + hasHandler(name: string, enabled = false) : boolean { + return enabled ? typeof this.enabledHandlers[name] !== 'undefined' : typeof this.handlers[name] !== 'undefined'; } /** @@ -234,5 +271,4 @@ export class CoreDelegate { updateData() { } - } \ No newline at end of file diff --git a/src/core/login/login.module.ts b/src/core/login/login.module.ts index e13f72e96..8e4f9a3d8 100644 --- a/src/core/login/login.module.ts +++ b/src/core/login/login.module.ts @@ -21,7 +21,7 @@ import { CoreLoginHelperProvider } from './providers/helper'; imports: [ ], providers: [ - CoreLoginHelperProvider, + CoreLoginHelperProvider ] }) export class CoreLoginModule {} diff --git a/src/core/login/pages/email-signup/email-signup.html b/src/core/login/pages/email-signup/email-signup.html index e90bddbb5..c3fd44236 100644 --- a/src/core/login/pages/email-signup/email-signup.html +++ b/src/core/login/pages/email-signup/email-signup.html @@ -75,14 +75,14 @@
- - + + + {{ category.name }} + + -
+ {{ 'core.login.security_question' | translate }} {{ 'core.login.recaptchachallengeimage' | translate }} @@ -96,20 +96,20 @@ {{ 'core.login.getanothercaptcha' | translate }} -
+ -
+ {{ 'core.login.policyagreement' | translate }}

{{ 'core.login.policyagreementclick' | translate }}

{{ 'core.login.policyaccept' | translate }} - + -
+ diff --git a/src/core/login/pages/email-signup/email-signup.module.ts b/src/core/login/pages/email-signup/email-signup.module.ts index 451715de6..422f4271f 100644 --- a/src/core/login/pages/email-signup/email-signup.module.ts +++ b/src/core/login/pages/email-signup/email-signup.module.ts @@ -18,6 +18,7 @@ import { CoreLoginEmailSignupPage } from './email-signup'; import { TranslateModule } from '@ngx-translate/core'; import { CoreComponentsModule } from '../../../../components/components.module'; import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { CoreUserComponentsModule } from '../../../user/components/components.module'; @NgModule({ declarations: [ @@ -26,6 +27,7 @@ import { CoreDirectivesModule } from '../../../../directives/directives.module'; imports: [ CoreComponentsModule, CoreDirectivesModule, + CoreUserComponentsModule, IonicPageModule.forChild(CoreLoginEmailSignupPage), TranslateModule.forChild() ] diff --git a/src/core/login/pages/email-signup/email-signup.ts b/src/core/login/pages/email-signup/email-signup.ts index 86bca81d8..c351f4aab 100644 --- a/src/core/login/pages/email-signup/email-signup.ts +++ b/src/core/login/pages/email-signup/email-signup.ts @@ -22,6 +22,7 @@ import { CoreUtilsProvider } from '../../../../providers/utils/utils'; import { CoreWSProvider } from '../../../../providers/ws'; import { CoreLoginHelperProvider } from '../../providers/helper'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { CoreUserProfileFieldDelegate } from '../../../user/providers/user-profile-field-delegate'; /** * Page to signup using email. @@ -55,7 +56,7 @@ export class CoreLoginEmailSignupPage { constructor(private navCtrl: NavController, navParams: NavParams, private fb: FormBuilder, private wsProvider: CoreWSProvider, private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider, - private textUtils: CoreTextUtilsProvider) { + private textUtils: CoreTextUtilsProvider, private userProfileFieldDelegate :CoreUserProfileFieldDelegate) { this.siteUrl = navParams.get('siteUrl'); @@ -228,26 +229,22 @@ export class CoreLoginEmailSignupPage { } // Get the data for the custom profile fields. - // @todo: Implement it once profile fields are implemented. - // $mmUserProfileFieldsDelegate.getDataForFields(fields, true, 'email', $scope.data).then(function(fieldsData) { - // params.customprofilefields = fieldsData; + this.userProfileFieldDelegate.getDataForFields(this.settings.profilefields, true, 'email', this.signupForm.value).then((fieldsData) => { + params.customprofilefields = fieldsData; - this.wsProvider.callAjax('auth_email_signup_user', params, {siteUrl: this.siteUrl}).then((result) => { - if (result.success) { - // Show alert and ho back. - let message = this.translate.instant('core.login.emailconfirmsent', {$a: params.email}); - this.domUtils.showAlert(this.translate.instant('core.success'), message); - this.navCtrl.pop(); - } else { - if (result.warnings && result.warnings.length) { - this.domUtils.showErrorModal(result.warnings[0].message); + this.wsProvider.callAjax('auth_email_signup_user', params, {siteUrl: this.siteUrl}).then((result) => { + if (result.success) { + // Show alert and ho back. + let message = this.translate.instant('core.login.emailconfirmsent', {$a: params.email}); + this.domUtils.showAlert(this.translate.instant('core.success'), message); + this.navCtrl.pop(); } else { - this.domUtils.showErrorModal('core.login.usernotaddederror', true); - } + this.domUtils.showErrorModalFirstWarning(result.warnings, 'core.login.usernotaddederror', true); - // Error sending, request another capctha since the current one is probably invalid now. - this.requestCaptcha(true); - } + // Error sending, request another capctha since the current one is probably invalid now. + this.requestCaptcha(true); + } + }); }).catch((error) => { this.domUtils.showErrorModalDefault(error && error.error, 'core.login.usernotaddederror', true); diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index 28db16e29..1706f28cc 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -244,12 +244,12 @@ export class CoreLoginHelperProvider { * @return {any} Categories with the fields to show in each one. */ formatProfileFieldsForSignup(profileFields: any[]) : any { - let categories = {}; - if (!profileFields) { - return categories; + return []; } + let categories = {}; + profileFields.forEach((field) => { if (!field.signup) { // Not a signup field, ignore it. @@ -267,7 +267,9 @@ export class CoreLoginHelperProvider { categories[field.categoryid].fields.push(field); }); - return categories; + return Object.keys(categories).map((index) => { + return categories[index]; + }); } /** diff --git a/src/core/user/components/user-profile-field/user-profile-field.ts b/src/core/user/components/user-profile-field/user-profile-field.ts index 1b3dcfee0..b79bdfd0f 100644 --- a/src/core/user/components/user-profile-field/user-profile-field.ts +++ b/src/core/user/components/user-profile-field/user-profile-field.ts @@ -15,6 +15,7 @@ import { Component, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentRef, OnInit } from '@angular/core'; import { CoreLoggerProvider } from '../../../../providers/logger'; import { CoreUserProfileFieldDelegate } from '../../providers/user-profile-field-delegate'; +import { CoreUtilsProvider } from '../../../../providers/utils/utils'; /** * Directive to render user profile field. @@ -27,13 +28,13 @@ export class CoreUserProfileFieldComponent implements OnInit { @Input() field: any; // The profile field to be rendered. @Input() signup?: boolean = false; // True if editing the field in signup. Defaults to false. @Input() edit?: boolean = false; // True if editing the field. Defaults to false. - @Input() model?: any; // Model where to store the data. Required if edit=true or signup=true. + @Input() form?: any; // Form where to add the form control. Required if edit=true or signup=true. @Input() registerAuth?: string; // Register auth method. E.g. 'email'. // Get the containers where to inject dynamic components. We use a setter because they might be inside a *ngIf. @ViewChild('userProfileField', { read: ViewContainerRef }) set userProfileField (el: ViewContainerRef) { if (this.field) { - this.createComponent(this.ufDelegate.getComponent(this.field, this.signup, this.registerAuth), el); + this.createComponent(this.ufDelegate.getComponent(this.field, this.signup), el); } else { // The component hasn't been initialized yet. Store the container. this.fieldContainer = el; @@ -47,7 +48,7 @@ export class CoreUserProfileFieldComponent implements OnInit { protected fieldInstance: any; constructor(logger: CoreLoggerProvider, private factoryResolver: ComponentFactoryResolver, - private ufDelegate: CoreUserProfileFieldDelegate) { + private ufDelegate: CoreUserProfileFieldDelegate, private utilsProvider: CoreUtilsProvider) { this.logger = logger.getInstance('CoreUserProfileFieldComponent'); } @@ -55,7 +56,7 @@ export class CoreUserProfileFieldComponent implements OnInit { * Component being initialized. */ ngOnInit() { - this.createComponent(this.ufDelegate.getComponent(this.field, this.signup, this.registerAuth), this.fieldContainer); + this.createComponent(this.ufDelegate.getComponent(this.field, this.signup), this.fieldContainer); } /** @@ -86,8 +87,12 @@ export class CoreUserProfileFieldComponent implements OnInit { // Set the Input data. this.fieldInstance.field = this.field; - this.fieldInstance.edit = this.edit; - this.fieldInstance.model = this.model; + this.fieldInstance.edit = this.utilsProvider.isTrueOrOne(this.edit); + if (this.edit) { + this.fieldInstance.signup = this.utilsProvider.isTrueOrOne(this.signup); + this.fieldInstance.disabled = this.utilsProvider.isTrueOrOne(this.field.locked); + this.fieldInstance.form = this.form; + } return true; } catch(ex) { diff --git a/src/core/user/lang/en.json b/src/core/user/lang/en.json index 6ea4eb277..d5390b8ac 100644 --- a/src/core/user/lang/en.json +++ b/src/core/user/lang/en.json @@ -8,8 +8,11 @@ "detailsnotavailable": "The details of this user are not available to you.", "editingteacher": "Teacher", "email": "Email address", + "emailagain": "Email (again)", "errorloaduser": "Error loading user.", + "firstname": "First name", "interests": "Interests", + "lastname": "Surname", "manager": "Manager", "newpicture": "New picture", "phone1": "Phone", diff --git a/src/core/user/providers/helper.ts b/src/core/user/providers/helper.ts index e09c223d5..e4d6eeb05 100644 --- a/src/core/user/providers/helper.ts +++ b/src/core/user/providers/helper.ts @@ -60,7 +60,6 @@ export class CoreUserHelperProvider { let separator = this.translate.instant('core.listsep'); roles.map((value) => { - console.error(value); let translation = this.translate.instant('core.user.' + value.shortname); return translation.indexOf('core.user.') < 0 ? translation : value.shortname; }); diff --git a/src/core/user/providers/user-profile-field-delegate.ts b/src/core/user/providers/user-profile-field-delegate.ts index b3b8784dd..72cbc2b5a 100644 --- a/src/core/user/providers/user-profile-field-delegate.ts +++ b/src/core/user/providers/user-profile-field-delegate.ts @@ -23,22 +23,19 @@ export interface CoreUserProfileFieldHandler extends CoreDelegateHandler { /** * Return the Component to use to display the user profile field. * - * @param {any} field User field to get the data for. - * @param {boolean} [signup] True if user is in signup page. - * @param {string} [registerAuth] Register auth method. E.g. 'email'. * @return {any} The component to use, undefined if not found. */ - getComponent(field: any, signup: boolean, registerAuth: string): any; + getComponent(): any; /** * Get the data to send for the field based on the input data. * @param {any} field User field to get the data for. * @param {boolean} signup True if user is in signup page. * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} model Model with the input data. + * @param {any} formValues Form Values. * @return {Promise|CoreUserProfileFieldHandlerData} Data to send for the field. */ - getData?(field: any, signup: boolean, registerAuth: string, model: any): + getData?(field: any, signup: boolean, registerAuth: string, formValues: any): Promise | CoreUserProfileFieldHandlerData; }; @@ -80,12 +77,15 @@ export class CoreUserProfileFieldDelegate extends CoreDelegate { * * @param {any} field User field to get the directive for. * @param {boolean} signup True if user is in signup page. - * @param {string} registerAuth Register auth method. E.g. 'email' * @return {any} The component to use, undefined if not found. */ - getComponent(field: any, signup: boolean, registerAuth: string) : any { + getComponent(field: any, signup: boolean) : any { let type = field.type || field.datatype; - return this.executeFunction(type, 'getComponent', [field, signup, registerAuth]); + if (signup) { + return this.executeFunction(type, 'getComponent'); + } else { + return this.executeFunctionOnEnabled(type, 'getComponent'); + } } /** @@ -94,22 +94,22 @@ export class CoreUserProfileFieldDelegate extends CoreDelegate { * @param {any} field User field to get the data for. * @param {boolean} signup True if user is in signup page. * @param {string} registerAuth Register auth method. E.g. 'email'. - * @param {any} model Model with the input data. + * @param {any} formValues Form values. * @return {Promise} Data to send for the field. */ - getDataForField(field: any, signup: boolean, registerAuth: string, model: any): Promise { - let handler = this.getHandler(field, signup); - + getDataForField(field: any, signup: boolean, registerAuth: string, formValues: any): Promise { + let type = field.type || field.datatype, + handler = this.getHandler(type, !signup); if (handler) { let name = 'profile_field_' + field.shortname; if (handler.getData) { - return Promise.resolve(handler.getData(field, signup, registerAuth, model)); - } else if (field.shortname && typeof model[name] != 'undefined') { - // Handler doesn't implement the function, but the model has data for the field. + return Promise.resolve(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 Promise.resolve({ - type: field.type || field.datatype, + type: type, name: name, - value: model[name] + value: formValues[name] }); } } @@ -122,43 +122,25 @@ export class CoreUserProfileFieldDelegate extends CoreDelegate { * @param {any[]} fields User fields to get the data for. * @param {boolean} [signup] True if user is in signup page. * @param {string} [registerAuth] Register auth method. E.g. 'email'. - * @param {any} model Model with the input data. + * @param {any} formValues Form values. * @return {Promise} Data to send. */ - getDataForFields(fields: any[], signup = false, registerAuth = "", model: any): Promise { + getDataForFields(fields: any[], signup = false, registerAuth = "", formValues: any): Promise { let result = [], promises = []; fields.forEach((field) => { - this.getDataForField(field, signup, registerAuth, model).then((data) => { - result.push(data); + promises.push(this.getDataForField(field, signup, registerAuth, formValues).then((data) => { + if (data) { + result.push(data); + } }).catch(() => { // Ignore errors. - }); + })); }); return Promise.all(promises).then(() => { return result; }); } - - /** - * Get a handler. - * - * @param {any} field User field to get the directive for. - * @param {boolean} signup True if user is in signup page. - * @return {any} Handler. - */ - protected getHandler(field: any, signup: boolean): any { - let type = field.type || field.datatype; - - if (signup) { - if (this.handlers[type]) { - return this.handlers[type]; - } - return false; - } - - return this.enabledHandlers[type]; - } } diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index 5e4df4f10..ae8918b95 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -743,6 +743,20 @@ export class CoreDomUtilsProvider { } } + /** + * Show an alert modal with the first warning error message. It uses a default message if error is not a string. + * + * @param {any} warnings Warnings returned. + * @param {any} [defaultError] Message to show if the error is not a string. + * @param {boolean} [needsTranslate] Whether the error needs to be translated. + * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. + * @return {Alert} The alert modal. + */ + showErrorModalFirstWarning(warnings: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number) : Alert { + let error = warnings && warnings.length && warnings[0].message; + return this.showErrorModalDefault(error, defaultError, needsTranslate, autocloseTime); + } + /** * Displays a loading modal window. * diff --git a/src/providers/utils/time.ts b/src/providers/utils/time.ts index 3382e77eb..69d62f5f0 100644 --- a/src/providers/utils/time.ts +++ b/src/providers/utils/time.ts @@ -145,4 +145,12 @@ export class CoreTimeUtilsProvider { return Math.round(Date.now() / 1000); } + /** + * Return the localized ISO format (i.e DDMMYY) from the localized moment format. Useful for translations. + * + * @return {string} Localized ISO format + */ + getLocalizedDateFormat(lozalizedFormat) : string { + return moment.localeData().longDateFormat(lozalizedFormat); + } } From 09e07c31fe6c1440fc1b28b388a8c0da6fdeff35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 23 Jan 2018 15:35:34 +0100 Subject: [PATCH 08/13] MOBILE-2317 user: Delete user from cache when deleted form site --- .../module-completion/module-completion.ts | 19 +++++++------- src/core/user/providers/user.ts | 26 +++++++++++++++++++ src/core/user/user.module.ts | 24 ++++++++++++++++- 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/core/course/components/module-completion/module-completion.ts b/src/core/course/components/module-completion/module-completion.ts index 1bd9e99a2..169363ca4 100644 --- a/src/core/course/components/module-completion/module-completion.ts +++ b/src/core/course/components/module-completion/module-completion.ts @@ -17,6 +17,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '../../../../providers/sites'; import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; +import { CoreUserProvider } from '../../../user/providers/user'; /** * Component to handle activity completion. It shows a checkbox with the current status, and allows manually changing @@ -39,8 +40,8 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { completionImage: string; completionDescription: string; - constructor(private textUtils: CoreTextUtilsProvider, private translate: TranslateService, - private domUtils: CoreDomUtilsProvider, private sitesProvider: CoreSitesProvider) { + constructor(private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider, + private translate: TranslateService, private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider) { this.completionChanged = new EventEmitter(); } @@ -130,13 +131,13 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { if (this.completion.overrideby > 0) { langKey += '-override'; - // @todo: Get user profile. - // promise = $mmUser.getProfile(scope.completion.overrideby, scope.completion.courseId, true).then(function(profile) { - // return { - // overrideuser: profile.fullname, - // modname: modNameFormatted - // }; - // }); + promise = this.userProvider.getProfile(this.completion.overrideby, this.completion.courseId, true).then( + (profile) => { + return { + overrideuser: profile.fullname, + modname: modNameFormatted + }; + }); } else { promise = Promise.resolve(modNameFormatted); } diff --git a/src/core/user/providers/user.ts b/src/core/user/providers/user.ts index b243db4c9..21c647b7f 100644 --- a/src/core/user/providers/user.ts +++ b/src/core/user/providers/user.ts @@ -79,6 +79,32 @@ export class CoreUserProvider { }); } + /** + * Store user basic information in local DB to be retrieved if the WS call fails. + * + * @param {number} userId User ID. + * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. + * @return {Promise} Promise resolve when the user is deleted. + */ + deleteStoredUser(userId: number, siteId?: string): Promise { + if (isNaN(userId)) { + return Promise.reject(null); + } + + let promises = []; + + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + // Invalidate WS calls. + promises.push(this.invalidateUserCache(userId, siteId)); + + promises.push(this.sitesProvider.getSite(siteId).then((site) => { + return site.getDb().deleteRecords(this.USERS_TABLE, { id: userId }); + })); + + return Promise.all(promises); + } + /** * Get user profile. The type of profile retrieved depends on the params. * diff --git a/src/core/user/user.module.ts b/src/core/user/user.module.ts index 474d9171a..89f35804b 100644 --- a/src/core/user/user.module.ts +++ b/src/core/user/user.module.ts @@ -18,6 +18,8 @@ import { CoreUserProfileFieldDelegate } from './providers/user-profile-field-del import { CoreUserProvider } from './providers/user'; import { CoreUserHelperProvider } from './providers/helper'; import { CoreUserProfileMailHandler } from './providers/user-handler'; +import { CoreEventsProvider } from '../../providers/events'; +import { CoreSitesProvider } from '../../providers/sites'; @NgModule({ declarations: [ @@ -33,7 +35,27 @@ import { CoreUserProfileMailHandler } from './providers/user-handler'; ] }) export class CoreUserModule { - constructor(userDelegate: CoreUserDelegate, userProfileMailHandler: CoreUserProfileMailHandler) { + constructor(userDelegate: CoreUserDelegate, userProfileMailHandler: CoreUserProfileMailHandler, + eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, userProvider: CoreUserProvider) { userDelegate.registerHandler(userProfileMailHandler); + + eventsProvider.on(CoreEventsProvider.USER_DELETED, (data) => { + // Search for userid in params. + let params = data.params, + userId = 0; + if (params.userid) { + userId = params.userid; + } else if (params.userids) { + userId = params.userids[0]; + } else if (params.field === 'id' && params.values && params.values.length) { + userId = params.values[0]; + } else if (params.userlist && params.userlist.length) { + userId = params.userlist[0].userid; + } + + if (userId > 0) { + userProvider.deleteStoredUser(userId, data.siteId); + } + }, sitesProvider.getCurrentSiteId()); } } From 96d548e706d8837535b6a7ed8cfbc9e98a3a9daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 24 Jan 2018 13:49:53 +0100 Subject: [PATCH 09/13] MOBILE-2317 user: Add user links handler --- .../pages/course-preview/course-preview.html | 13 ++-- src/core/user/providers/helper.ts | 6 +- src/core/user/providers/user-link-handler.ts | 69 +++++++++++++++++++ src/core/user/user.module.ts | 9 ++- 4 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 src/core/user/providers/user-link-handler.ts diff --git a/src/core/courses/pages/course-preview/course-preview.html b/src/core/courses/pages/course-preview/course-preview.html index 452b2e79f..ec071512d 100644 --- a/src/core/courses/pages/course-preview/course-preview.html +++ b/src/core/courses/pages/course-preview/course-preview.html @@ -21,19 +21,20 @@ - -

{{ 'core.teachers' | translate }}

-

{{contact.fullname}}

-
+ + {{ 'core.teachers' | translate }} + {{contact.fullname}} + +
-

{{ instance.name }}

+

{{ instance.name }}

-

{{ 'core.courses.paypalaccepted' | translate }}

+

{{ 'core.courses.paypalaccepted' | translate }}

{{ 'core.paymentinstant' | translate }}

diff --git a/src/core/user/providers/helper.ts b/src/core/user/providers/helper.ts index e4d6eeb05..ba62a3f00 100644 --- a/src/core/user/providers/helper.ts +++ b/src/core/user/providers/helper.ts @@ -59,11 +59,9 @@ export class CoreUserHelperProvider { let separator = this.translate.instant('core.listsep'); - roles.map((value) => { + return roles.map((value) => { let translation = this.translate.instant('core.user.' + value.shortname); return translation.indexOf('core.user.') < 0 ? translation : value.shortname; - }); - - return roles.join(separator + " "); + }).join(separator + " "); } } diff --git a/src/core/user/providers/user-link-handler.ts b/src/core/user/providers/user-link-handler.ts new file mode 100644 index 000000000..bd2515783 --- /dev/null +++ b/src/core/user/providers/user-link-handler.ts @@ -0,0 +1,69 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { CoreContentLinksHandlerBase } from '../../contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '../../contentlinks/providers/delegate'; +import { CoreLoginHelperProvider } from '../../login/providers/helper'; + +/** + * Handler to treat links to user profiles. + */ +@Injectable() +export class CoreUserProfileLinkHandler extends CoreContentLinksHandlerBase { + name = 'CoreUserProfileLinkHandler'; + // Match user/view.php and user/profile.php but NOT grade/report/user/. + pattern = /((\/user\/view\.php)|(\/user\/profile\.php)).*([\?\&]id=\d+)/; + + constructor(private loginHelper: CoreLoginHelperProvider) { + super(); + } + + /** + * Get the list of actions for a link (url). + * + * @param {string[]} siteIds List of sites the URL belongs to. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: any, courseId?: number) : + CoreContentLinksAction[]|Promise { + return [{ + action: (siteId, navCtrl?) => { + let stateParams = { + courseId: params.course, + userId: parseInt(params.id, 10) + }; + // Always use redirect to make it the new history root (to avoid "loops" in history). + this.loginHelper.redirect('CoreUserProfilePage', stateParams, siteId); + } + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param {string} siteId The site ID. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number) : boolean|Promise { + return url.indexOf('/grade/report/') == -1; + } +} diff --git a/src/core/user/user.module.ts b/src/core/user/user.module.ts index 89f35804b..62fb1af70 100644 --- a/src/core/user/user.module.ts +++ b/src/core/user/user.module.ts @@ -20,6 +20,8 @@ import { CoreUserHelperProvider } from './providers/helper'; import { CoreUserProfileMailHandler } from './providers/user-handler'; import { CoreEventsProvider } from '../../providers/events'; import { CoreSitesProvider } from '../../providers/sites'; +import { CoreContentLinksDelegate } from '../contentlinks/providers/delegate'; +import { CoreUserProfileLinkHandler } from './providers/user-link-handler'; @NgModule({ declarations: [ @@ -31,13 +33,16 @@ import { CoreSitesProvider } from '../../providers/sites'; CoreUserProfileFieldDelegate, CoreUserProfileMailHandler, CoreUserProvider, - CoreUserHelperProvider + CoreUserHelperProvider, + CoreUserProfileLinkHandler ] }) export class CoreUserModule { constructor(userDelegate: CoreUserDelegate, userProfileMailHandler: CoreUserProfileMailHandler, - eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, userProvider: CoreUserProvider) { + eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, userProvider: CoreUserProvider, + contentLinksDelegate: CoreContentLinksDelegate, userLinkHandler: CoreUserProfileLinkHandler) { userDelegate.registerHandler(userProfileMailHandler); + contentLinksDelegate.registerHandler(userLinkHandler); eventsProvider.on(CoreEventsProvider.USER_DELETED, (data) => { // Search for userid in params. From debfb5c6c6e1116c0ca3bf5acd23c5da2522858b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 25 Jan 2018 13:19:11 +0100 Subject: [PATCH 10/13] MOBILE-2317 user: Solve PR errors and lints --- src/addon/calendar/calendar.module.ts | 2 +- src/addon/calendar/pages/event/event.ts | 6 +-- src/addon/calendar/pages/list/list.ts | 8 ++-- src/addon/calendar/providers/helper.ts | 3 +- .../checkbox/component/checkbox.ts | 8 ++-- .../checkbox/providers/handler.ts | 3 +- .../datetime/component/datetime.ts | 8 ++-- .../datetime/providers/handler.ts | 6 +-- .../userprofilefield/menu/component/menu.ts | 3 +- .../menu/providers/handler.ts | 3 +- .../userprofilefield/text/component/text.ts | 8 ++-- .../text/providers/handler.ts | 3 +- .../textarea/component/textarea.ts | 16 +++---- .../textarea/providers/handler.ts | 3 +- src/classes/delegate.ts | 14 ++++--- .../course-picker-menu-popover.ts | 2 +- .../rich-text-editor/rich-text-editor.ts | 3 +- src/components/split-view/split-view.ts | 3 +- src/components/tabs/tab.ts | 3 +- src/components/tabs/tabs.ts | 2 +- src/core/contentlinks/providers/helper.ts | 2 +- .../unsupported-module/unsupported-module.ts | 6 +-- .../unsupported-module/unsupported-module.ts | 5 +-- src/core/course/providers/helper.ts | 2 +- .../providers/module-prefetch-delegate.ts | 4 +- .../course-list-item/course-list-item.ts | 3 +- .../course-progress/course-progress.ts | 3 +- .../providers/my-overview-link-handler.ts | 3 +- src/core/emulator/providers/file.ts | 9 ++-- .../emulator/providers/local-notifications.ts | 3 +- src/core/fileuploader/providers/helper.ts | 3 +- .../login/pages/email-signup/email-signup.ts | 3 +- src/core/mainmenu/providers/delegate.ts | 2 +- .../all-course-list/all-course-list.ts | 3 +- .../components/categories/categories.ts | 3 +- .../components/course-search/course-search.ts | 3 +- .../enrolled-course-list.ts | 3 +- src/core/sitehome/components/index/index.ts | 3 +- src/core/sitehome/components/news/news.ts | 3 +- .../user-profile-field/user-profile-field.ts | 2 +- src/core/user/pages/about/about.html | 11 ++--- src/core/user/pages/about/about.ts | 3 +- src/core/user/pages/profile/profile.html | 4 +- src/core/user/pages/profile/profile.ts | 9 ++-- src/core/user/providers/user-delegate.ts | 10 ++--- src/core/user/providers/user-handler.ts | 2 +- src/core/user/providers/user-link-handler.ts | 8 ++-- src/core/user/providers/user.ts | 42 ++++++++++--------- src/directives/format-text.ts | 6 ++- src/directives/user-link.ts | 2 +- src/providers/sites.ts | 14 +++++++ src/providers/utils/dom.ts | 2 +- src/providers/utils/text.ts | 2 +- src/providers/ws.ts | 3 +- 54 files changed, 158 insertions(+), 135 deletions(-) diff --git a/src/addon/calendar/calendar.module.ts b/src/addon/calendar/calendar.module.ts index b36d0bf0c..a0feb1722 100644 --- a/src/addon/calendar/calendar.module.ts +++ b/src/addon/calendar/calendar.module.ts @@ -46,7 +46,7 @@ export class AddonCalendarModule { localNotificationsProvider.registerClick(AddonCalendarProvider.COMPONENT, (data) => { if (data.eventid) { initDelegate.ready().then(() => { - calendarProvider.isDisabled(data.siteId).then(function(disabled) { + calendarProvider.isDisabled(data.siteId).then((disabled) => { if (disabled) { // The calendar is disabled in the site, don't open it. return; diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts index 2778df277..4bce81ca5 100644 --- a/src/addon/calendar/pages/event/event.ts +++ b/src/addon/calendar/pages/event/event.ts @@ -45,10 +45,10 @@ export class AddonCalendarEventPage { courseName: string; notificationsEnabled = false; - constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, private navParams: NavParams, + constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams, private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, - private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider, - private localNotificationsProvider: CoreLocalNotificationsProvider, private courseProvider: CoreCourseProvider) { + private calendarHelper: AddonCalendarHelperProvider, sitesProvider: CoreSitesProvider, + localNotificationsProvider: CoreLocalNotificationsProvider, private courseProvider: CoreCourseProvider) { this.eventId = navParams.get('id'); this.notificationsEnabled = localNotificationsProvider.isAvailable(); diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 5e6a2fbd1..fb567453c 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -63,11 +63,11 @@ export class AddonCalendarListPage implements OnDestroy { course: this.allCourses }; - constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, private navParams: NavParams, + constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams, private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider, - private calendarHelper: AddonCalendarHelperProvider, private sitesProvider: CoreSitesProvider, - private localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController, - private eventsProvider: CoreEventsProvider, private navCtrl: NavController, private appProvider: CoreAppProvider) { + private calendarHelper: AddonCalendarHelperProvider, sitesProvider: CoreSitesProvider, + localNotificationsProvider: CoreLocalNotificationsProvider, private popoverCtrl: PopoverController, + eventsProvider: CoreEventsProvider, private navCtrl: NavController, appProvider: CoreAppProvider) { this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); this.notificationsEnabled = localNotificationsProvider.isAvailable(); diff --git a/src/addon/calendar/providers/helper.ts b/src/addon/calendar/providers/helper.ts index c0b578986..e0e08da98 100644 --- a/src/addon/calendar/providers/helper.ts +++ b/src/addon/calendar/providers/helper.ts @@ -14,7 +14,6 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '../../../providers/logger'; -import { CoreSitesProvider } from '../../../providers/sites'; import { CoreCourseProvider } from '../../../core/course/providers/course'; /** @@ -32,7 +31,7 @@ export class AddonCalendarHelperProvider { 'category': 'albums' }; - constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider) { + constructor(logger: CoreLoggerProvider, private courseProvider: CoreCourseProvider) { this.logger = logger.getInstance('AddonCalendarHelperProvider'); } diff --git a/src/addon/userprofilefield/checkbox/component/checkbox.ts b/src/addon/userprofilefield/checkbox/component/checkbox.ts index b909bfac7..f04dabb92 100644 --- a/src/addon/userprofilefield/checkbox/component/checkbox.ts +++ b/src/addon/userprofilefield/checkbox/component/checkbox.ts @@ -14,6 +14,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { CoreUtilsProvider } from '../../../../providers/utils/utils'; /** * Directive to render a checkbox user profile field. @@ -28,7 +29,7 @@ export class AddonUserProfileFieldCheckboxComponent implements OnInit { @Input() disabled?: boolean = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. - constructor(private fb: FormBuilder) {} + constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) {} /** * Component being initialized. @@ -41,10 +42,11 @@ export class AddonUserProfileFieldCheckboxComponent implements OnInit { // Initialize the value. let formData = { - value: field.defaultdata && field.defaultdata !== '0' && field.defaultdata !== 'false', + value: this.utils.isTrueOrOne(field.defaultdata), disabled: this.disabled }; - this.form.addControl(field.modelName, this.fb.control(formData, field.required && !field.locked ? Validators.requiredTrue : null)); + this.form.addControl(field.modelName, this.fb.control(formData, + field.required && !field.locked ? Validators.requiredTrue : null)); } } diff --git a/src/addon/userprofilefield/checkbox/providers/handler.ts b/src/addon/userprofilefield/checkbox/providers/handler.ts index ee5147f99..d6547bf96 100644 --- a/src/addon/userprofilefield/checkbox/providers/handler.ts +++ b/src/addon/userprofilefield/checkbox/providers/handler.ts @@ -13,7 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '../../../../core/user/providers/user-profile-field-delegate'; +import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from + '../../../../core/user/providers/user-profile-field-delegate'; import { AddonUserProfileFieldCheckboxComponent } from '../component/checkbox'; /** diff --git a/src/addon/userprofilefield/datetime/component/datetime.ts b/src/addon/userprofilefield/datetime/component/datetime.ts index 737b6bed7..b7408eb1b 100644 --- a/src/addon/userprofilefield/datetime/component/datetime.ts +++ b/src/addon/userprofilefield/datetime/component/datetime.ts @@ -15,6 +15,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { CoreTimeUtilsProvider } from '../../../../providers/utils/time'; +import { CoreUtilsProvider } from '../../../../providers/utils/utils'; /** * Directive to render a datetime user profile field. @@ -29,7 +30,7 @@ export class AddonUserProfileFieldDatetimeComponent implements OnInit { @Input() disabled?: boolean = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. - constructor(private fb: FormBuilder, private timeUtils: CoreTimeUtilsProvider) {} + constructor(private fb: FormBuilder, private timeUtils: CoreTimeUtilsProvider, protected utils: CoreUtilsProvider) {} /** * Component being initialized. @@ -41,7 +42,7 @@ export class AddonUserProfileFieldDatetimeComponent implements OnInit { field.modelName = 'profile_field_' + field.shortname; // Check if it's only date or it has time too. - let hasTime = field.param3 && field.param3 !== '0' && field.param3 !== 'false'; + let hasTime = this.utils.isTrueOrOne(field.param3); field.format = hasTime ? this.timeUtils.getLocalizedDateFormat('LLL') : this.timeUtils.getLocalizedDateFormat('LL'); // Check min value. @@ -64,7 +65,8 @@ export class AddonUserProfileFieldDatetimeComponent implements OnInit { value: field.defaultdata, disabled: this.disabled }; - this.form.addControl(field.modelName, this.fb.control(formData, field.required && !field.locked ? Validators.required : null)); + this.form.addControl(field.modelName, this.fb.control(formData, + field.required && !field.locked ? Validators.required : null)); } } diff --git a/src/addon/userprofilefield/datetime/providers/handler.ts b/src/addon/userprofilefield/datetime/providers/handler.ts index 16e1f7048..34f65bb25 100644 --- a/src/addon/userprofilefield/datetime/providers/handler.ts +++ b/src/addon/userprofilefield/datetime/providers/handler.ts @@ -13,9 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '../../../../core/user/providers/user-profile-field-delegate'; +import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from + '../../../../core/user/providers/user-profile-field-delegate'; import { AddonUserProfileFieldDatetimeComponent } from '../component/datetime'; -import { Platform } from 'ionic-angular'; /** * Datetime user profile field handlers. @@ -24,7 +24,7 @@ import { Platform } from 'ionic-angular'; export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFieldHandler { name = 'datetime'; - constructor(private platform: Platform) {} + constructor() {} /** * Whether or not the handler is enabled on a site level. diff --git a/src/addon/userprofilefield/menu/component/menu.ts b/src/addon/userprofilefield/menu/component/menu.ts index a7048af52..09c44f1f5 100644 --- a/src/addon/userprofilefield/menu/component/menu.ts +++ b/src/addon/userprofilefield/menu/component/menu.ts @@ -51,7 +51,8 @@ export class AddonUserProfileFieldMenuComponent implements OnInit { disabled: this.disabled }; // Initialize the value using default data. - this.form.addControl(field.modelName, this.fb.control(formData, field.required && !field.locked ? Validators.required : null)); + this.form.addControl(field.modelName, this.fb.control(formData, + field.required && !field.locked ? Validators.required : null)); } } diff --git a/src/addon/userprofilefield/menu/providers/handler.ts b/src/addon/userprofilefield/menu/providers/handler.ts index 7a04a2b61..9004df1fc 100644 --- a/src/addon/userprofilefield/menu/providers/handler.ts +++ b/src/addon/userprofilefield/menu/providers/handler.ts @@ -13,7 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '../../../../core/user/providers/user-profile-field-delegate'; +import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from + '../../../../core/user/providers/user-profile-field-delegate'; import { AddonUserProfileFieldMenuComponent } from '../component/menu'; /** diff --git a/src/addon/userprofilefield/text/component/text.ts b/src/addon/userprofilefield/text/component/text.ts index 3efb230e5..8ef5e8f2c 100644 --- a/src/addon/userprofilefield/text/component/text.ts +++ b/src/addon/userprofilefield/text/component/text.ts @@ -14,6 +14,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { CoreUtilsProvider } from '../../../../providers/utils/utils'; /** * Directive to render a text user profile field. @@ -28,7 +29,7 @@ export class AddonUserProfileFieldTextComponent implements OnInit { @Input() disabled?: boolean = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. - constructor(private fb: FormBuilder) {} + constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) {} /** * Component being initialized. @@ -45,14 +46,15 @@ export class AddonUserProfileFieldTextComponent implements OnInit { } // Check if it's a password or text. - field.inputType = field.param3 && field.param3 !== '0' && field.param3 !== 'false' ? 'password' : 'text'; + field.inputType = this.utils.isTrueOrOne(field.param3) ? 'password' : 'text'; let formData = { value: field.defaultdata, disabled: this.disabled }; // Initialize the value using default data. - this.form.addControl(field.modelName, this.fb.control(formData, field.required && !field.locked ? Validators.required : null)); + this.form.addControl(field.modelName, this.fb.control(formData, + field.required && !field.locked ? Validators.required : null)); } } diff --git a/src/addon/userprofilefield/text/providers/handler.ts b/src/addon/userprofilefield/text/providers/handler.ts index cd6754e70..b3778b173 100644 --- a/src/addon/userprofilefield/text/providers/handler.ts +++ b/src/addon/userprofilefield/text/providers/handler.ts @@ -13,7 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '../../../../core/user/providers/user-profile-field-delegate'; +import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from + '../../../../core/user/providers/user-profile-field-delegate'; import { AddonUserProfileFieldTextComponent } from '../component/text'; import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; diff --git a/src/addon/userprofilefield/textarea/component/textarea.ts b/src/addon/userprofilefield/textarea/component/textarea.ts index dbcf50dfc..927dd128e 100644 --- a/src/addon/userprofilefield/textarea/component/textarea.ts +++ b/src/addon/userprofilefield/textarea/component/textarea.ts @@ -40,15 +40,15 @@ export class AddonUserProfileFieldTextareaComponent implements OnInit { if (field && this.edit && this.form) { field.modelName = 'profile_field_' + field.shortname; + + let formData = { + value: field.defaultdata, + disabled: this.disabled + }; + + this.control = new FormControl(formData, field.required && !field.locked ? Validators.required : null); + this.form.addControl(field.modelName, this.control); } - - let formData = { - value: field.defaultdata, - disabled: this.disabled - }; - - this.control = new FormControl(formData, field.required && !field.locked ? Validators.required : null); - this.form.addControl(field.modelName, this.control); } } \ No newline at end of file diff --git a/src/addon/userprofilefield/textarea/providers/handler.ts b/src/addon/userprofilefield/textarea/providers/handler.ts index 9604e9a6e..1047cbfdc 100644 --- a/src/addon/userprofilefield/textarea/providers/handler.ts +++ b/src/addon/userprofilefield/textarea/providers/handler.ts @@ -13,7 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '../../../../core/user/providers/user-profile-field-delegate'; +import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from + '../../../../core/user/providers/user-profile-field-delegate'; import { AddonUserProfileFieldTextareaComponent } from '../component/textarea'; import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; diff --git a/src/classes/delegate.ts b/src/classes/delegate.ts index d9d472fd2..6b34d75ca 100644 --- a/src/classes/delegate.ts +++ b/src/classes/delegate.ts @@ -39,7 +39,7 @@ export class CoreDelegate { /** * Logger instance get from CoreLoggerProvider. - * @type {function} + * @type {any} */ protected logger; @@ -84,7 +84,6 @@ export class CoreDelegate { constructor(delegateName: string, protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, protected eventsProvider: CoreEventsProvider) { this.logger = this.loggerProvider.getInstance(delegateName); - this.sitesProvider = sitesProvider; // Update handlers on this cases. eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this)); @@ -151,9 +150,9 @@ export class CoreDelegate { * * @param {string} name The handler name. * @param {boolean} [enabled] Only enabled, or any. - * @return {boolean} If the controller is installed or not. + * @return {boolean} If the handler is registered or not. */ - hasHandler(name: string, enabled = false) : boolean { + hasHandler(name: string, enabled = false): boolean { return enabled ? typeof this.enabledHandlers[name] !== 'undefined' : typeof this.handlers[name] !== 'undefined'; } @@ -172,9 +171,12 @@ export class CoreDelegate { } /** - * Register a profile handler. + * Register a handler. + * + * @param {CoreDelegateHandler} handler The handler delegate object to register. + * @return {boolean} True when registered, false if already registered. */ - registerHandler(handler: CoreDelegateHandler) { + registerHandler(handler: CoreDelegateHandler): boolean { if (typeof this.handlers[handler.name] !== 'undefined') { this.logger.log(`Addon '${handler.name}' already registered`); return false; diff --git a/src/components/course-picker-menu/course-picker-menu-popover.ts b/src/components/course-picker-menu/course-picker-menu-popover.ts index e654664dc..fec561f64 100644 --- a/src/components/course-picker-menu/course-picker-menu-popover.ts +++ b/src/components/course-picker-menu/course-picker-menu-popover.ts @@ -26,7 +26,7 @@ export class CoreCoursePickerMenuPopoverComponent { courses: any[]; courseId = -1; - constructor(private navParams: NavParams, private viewCtrl: ViewController) { + constructor(navParams: NavParams, private viewCtrl: ViewController) { this.courses = navParams.get('courses') || []; this.courseId = navParams.get('courseId') || -1; } diff --git a/src/components/rich-text-editor/rich-text-editor.ts b/src/components/rich-text-editor/rich-text-editor.ts index 3f41f9f49..41a2df9c3 100644 --- a/src/components/rich-text-editor/rich-text-editor.ts +++ b/src/components/rich-text-editor/rich-text-editor.ts @@ -29,8 +29,7 @@ import { Keyboard } from '@ionic-native/keyboard'; * could be easily broken. * * Example: - * - * + * * * In the example above, the text written in the editor will be stored in newpost.text. */ diff --git a/src/components/split-view/split-view.ts b/src/components/split-view/split-view.ts index b6f20244a..3c65bee2b 100644 --- a/src/components/split-view/split-view.ts +++ b/src/components/split-view/split-view.ts @@ -14,9 +14,8 @@ // Code based on https://github.com/martinpritchardelevate/ionic-split-pane-demo -import { Component, ViewChild, Injectable, Input, ElementRef, OnInit } from '@angular/core'; +import { Component, ViewChild, Input, ElementRef, OnInit } from '@angular/core'; import { NavController, Nav } from 'ionic-angular'; -import { CoreSplitViewPlaceholderPage } from './placeholder/placeholder'; /** * Directive to create a split view layout. diff --git a/src/components/tabs/tab.ts b/src/components/tabs/tab.ts index 1435f2247..efeb1ef4d 100644 --- a/src/components/tabs/tab.ts +++ b/src/components/tabs/tab.ts @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, Output, OnInit, OnDestroy, ElementRef, EventEmitter, ContentChild, TemplateRef, - ViewChild } from '@angular/core'; +import { Component, Input, Output, OnInit, OnDestroy, ElementRef, EventEmitter, ContentChild, TemplateRef } from '@angular/core'; import { CoreTabsComponent } from './tabs'; import { Content } from 'ionic-angular'; diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts index 2fca81ca0..2ad6402d2 100644 --- a/src/components/tabs/tabs.ts +++ b/src/components/tabs/tabs.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, Input, Output, EventEmitter, OnInit, OnChanges, AfterViewInit, ViewChild, ElementRef, - SimpleChange } from '@angular/core'; + SimpleChange } from '@angular/core'; import { CoreTabComponent } from './tab'; import { Content } from 'ionic-angular'; diff --git a/src/core/contentlinks/providers/helper.ts b/src/core/contentlinks/providers/helper.ts index 3033cc368..35768d6f7 100644 --- a/src/core/contentlinks/providers/helper.ts +++ b/src/core/contentlinks/providers/helper.ts @@ -72,7 +72,7 @@ export class CoreContentLinksHelperProvider { */ goInSite(navCtrl: NavController, pageName: string, pageParams: any, siteId?: string) : void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - if (siteId == this.sitesProvider.getCurrentSiteId()) { + if (navCtrl && siteId == this.sitesProvider.getCurrentSiteId()) { navCtrl.push(pageName, pageParams); } else { this.loginHelper.redirect(pageName, pageParams, siteId); diff --git a/src/core/course/components/unsupported-module/unsupported-module.ts b/src/core/course/components/unsupported-module/unsupported-module.ts index 1c1910d87..7c4fca123 100644 --- a/src/core/course/components/unsupported-module/unsupported-module.ts +++ b/src/core/course/components/unsupported-module/unsupported-module.ts @@ -13,9 +13,6 @@ // limitations under the License. import { Component, Input, OnInit } from '@angular/core'; -import { IonicPage, NavParams } from 'ionic-angular'; -import { TranslateService } from '@ngx-translate/core'; -import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; import { CoreCourseProvider } from '../../providers/course'; import { CoreCourseModuleDelegate } from '../../providers/module-delegate'; @@ -34,8 +31,7 @@ export class CoreCourseUnsupportedModuleComponent implements OnInit { isSupportedByTheApp: boolean; moduleName: string; - constructor(navParams: NavParams, private translate: TranslateService, private textUtils: CoreTextUtilsProvider, - private courseProvider: CoreCourseProvider, private moduleDelegate: CoreCourseModuleDelegate) {} + constructor(private courseProvider: CoreCourseProvider, private moduleDelegate: CoreCourseModuleDelegate) {} /** * Component being initialized. diff --git a/src/core/course/pages/unsupported-module/unsupported-module.ts b/src/core/course/pages/unsupported-module/unsupported-module.ts index 9d2be19fe..3cbe20d7c 100644 --- a/src/core/course/pages/unsupported-module/unsupported-module.ts +++ b/src/core/course/pages/unsupported-module/unsupported-module.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component } from '@angular/core'; -import { IonicPage, NavParams, NavController } from 'ionic-angular'; +import { IonicPage, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; @@ -28,8 +28,7 @@ import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; export class CoreCourseUnsupportedModulePage { module: any; - constructor(navParams: NavParams, private translate: TranslateService, private textUtils: CoreTextUtilsProvider, - private navCtrl: NavController) { + constructor(navParams: NavParams, private translate: TranslateService, private textUtils: CoreTextUtilsProvider) { this.module = navParams.get('module') || {}; } diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 226ca0d19..fbd6ad427 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -25,7 +25,7 @@ import { CoreCoursesDelegate, CoreCoursesHandlerToDisplay } from '../../courses/ import { CoreSiteHomeProvider } from '../../sitehome/providers/sitehome'; import { CoreCourseProvider } from './course'; import { CoreCourseModuleDelegate } from './module-delegate'; -import { CoreCourseModulePrefetchDelegate, CoreCourseModulePrefetchHandler } from './module-prefetch-delegate'; +import { CoreCourseModulePrefetchDelegate } from './module-prefetch-delegate'; import { CoreLoginHelperProvider } from '../../login/providers/helper'; import { CoreConstants } from '../../constants'; import { CoreSite } from '../../../classes/site'; diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index 50b44fb64..39b492d7f 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { NavController } from 'ionic-angular'; import { CoreEventsProvider } from '../../../providers/events'; import { CoreFileProvider } from '../../../providers/file'; import { CoreFilepoolProvider } from '../../../providers/filepool'; @@ -957,8 +956,7 @@ export class CoreCourseModulePrefetchDelegate { * @return {Promise} Promise resolved with true if downloadable, false otherwise. */ isModuleDownloadable(module: any, courseId: number) : Promise { - let handler = this.getPrefetchHandlerFor(module), - promise; + let handler = this.getPrefetchHandlerFor(module); if (handler) { if (typeof handler.isDownloadable == 'function') { diff --git a/src/core/courses/components/course-list-item/course-list-item.ts b/src/core/courses/components/course-list-item/course-list-item.ts index 9e67707f1..bdc378218 100644 --- a/src/core/courses/components/course-list-item/course-list-item.ts +++ b/src/core/courses/components/course-list-item/course-list-item.ts @@ -31,7 +31,8 @@ import { CoreCoursesProvider } from '../../providers/courses'; export class CoreCoursesCourseListItemComponent implements OnInit { @Input() course: any; // The course to render. - constructor(private navCtrl: NavController, private translate: TranslateService, private coursesProvider: CoreCoursesProvider) {} + constructor(private navCtrl: NavController, private translate: TranslateService, private coursesProvider: CoreCoursesProvider) { + } /** * Component being initialized. diff --git a/src/core/courses/components/course-progress/course-progress.ts b/src/core/courses/components/course-progress/course-progress.ts index 2084e6113..b5052c506 100644 --- a/src/core/courses/components/course-progress/course-progress.ts +++ b/src/core/courses/components/course-progress/course-progress.ts @@ -14,7 +14,6 @@ import { Component, Input, OnInit, OnDestroy } from '@angular/core'; import { NavController } from 'ionic-angular'; -import { TranslateService } from '@ngx-translate/core'; import { CoreEventsProvider } from '../../../../providers/events'; import { CoreSitesProvider } from '../../../../providers/sites'; import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; @@ -45,7 +44,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { protected isDestroyed = false; protected courseStatusObserver; - constructor(private navCtrl: NavController, private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, + constructor(private navCtrl: NavController, private courseHelper: CoreCourseHelperProvider, private courseFormatDelegate: CoreCourseFormatDelegate, private domUtils: CoreDomUtilsProvider, private courseProvider: CoreCourseProvider, eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider) { // Listen for status change in course. diff --git a/src/core/courses/providers/my-overview-link-handler.ts b/src/core/courses/providers/my-overview-link-handler.ts index 28905b628..fb9d1f648 100644 --- a/src/core/courses/providers/my-overview-link-handler.ts +++ b/src/core/courses/providers/my-overview-link-handler.ts @@ -16,7 +16,6 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '../../contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '../../contentlinks/providers/delegate'; import { CoreLoginHelperProvider } from '../../login/providers/helper'; -import { CoreCoursesProvider } from './courses'; /** * Handler to treat links to my overview. @@ -27,7 +26,7 @@ export class CoreCoursesMyOverviewLinkHandler extends CoreContentLinksHandlerBas featureName = '$mmSideMenuDelegate_mmCourses'; pattern = /\/my\/?$/; - constructor(private coursesProvider: CoreCoursesProvider, private loginHelper: CoreLoginHelperProvider) { + constructor(private loginHelper: CoreLoginHelperProvider) { super(); } diff --git a/src/core/emulator/providers/file.ts b/src/core/emulator/providers/file.ts index 70c61ba0d..4ffb1a4c5 100644 --- a/src/core/emulator/providers/file.ts +++ b/src/core/emulator/providers/file.ts @@ -384,7 +384,8 @@ export class FileMock extends File { * @param {string} dirName The source directory name. * @param {string} newPath The destionation path to the directory. * @param {string} newDirName The destination directory name. - * @returns {Promise} Returns a Promise that resolves to the new DirectoryEntry object or rejects with an error. + * @returns {Promise} Returns a Promise that resolves to the new DirectoryEntry object or rejects with + * an error. */ moveDir(path: string, dirName: string, newPath: string, newDirName: string): Promise { return this.resolveDirectoryUrl(path).then((fse) => { @@ -421,7 +422,8 @@ export class FileMock extends File { * Read file and return data as an ArrayBuffer. * @param {string} path Base FileSystem. * @param {string} file Name of file, relative to path. - * @returns {Promise} Returns a Promise that resolves with the contents of the file as ArrayBuffer or rejects with an error. + * @returns {Promise} Returns a Promise that resolves with the contents of the file as ArrayBuffer or rejects + * with an error. */ readAsArrayBuffer(path: string, file: string): Promise { return this.readFileMock(path, file, 'ArrayBuffer'); @@ -444,7 +446,8 @@ export class FileMock extends File { * @param {string} path Base FileSystem. * @param {string} file Name of file, relative to path. - * @returns {Promise} Returns a Promise that resolves with the contents of the file as data URL or rejects with an error. + * @returns {Promise} Returns a Promise that resolves with the contents of the file as data URL or rejects + * with an error. */ readAsDataURL(path: string, file: string): Promise { return this.readFileMock(path, file, 'DataURL'); diff --git a/src/core/emulator/providers/local-notifications.ts b/src/core/emulator/providers/local-notifications.ts index 2d8e27af1..5f0e23abb 100644 --- a/src/core/emulator/providers/local-notifications.ts +++ b/src/core/emulator/providers/local-notifications.ts @@ -712,7 +712,8 @@ export class LocalNotificationsMock extends LocalNotifications { let tileNotif = new this.winNotif.TileNotification({ tag: notification.id + '', template: this.tileTemplate, - strings: [notification.title, notification.text, notification.title, notification.text, notification.title, notification.text], + strings: [notification.title, notification.text, notification.title, notification.text, notification.title, + notification.text], expirationTime: new Date(Date.now() + CoreConstants.SECONDS_HOUR * 1000) // Expire in 1 hour. }) diff --git a/src/core/fileuploader/providers/helper.ts b/src/core/fileuploader/providers/helper.ts index 438ee93c9..3573eba52 100644 --- a/src/core/fileuploader/providers/helper.ts +++ b/src/core/fileuploader/providers/helper.ts @@ -65,7 +65,8 @@ export class CoreFileUploaderHelperProvider { } wifiThreshold = typeof wifiThreshold == 'undefined' ? CoreFileUploaderProvider.WIFI_SIZE_WARNING : wifiThreshold; - limitedThreshold = typeof limitedThreshold == 'undefined' ? CoreFileUploaderProvider.LIMITED_SIZE_WARNING : limitedThreshold; + limitedThreshold = typeof limitedThreshold == 'undefined' ? + CoreFileUploaderProvider.LIMITED_SIZE_WARNING : limitedThreshold; if (size < 0) { return this.domUtils.showConfirm(this.translate.instant('core.fileuploader.confirmuploadunknownsize')); diff --git a/src/core/login/pages/email-signup/email-signup.ts b/src/core/login/pages/email-signup/email-signup.ts index c351f4aab..e8853c67b 100644 --- a/src/core/login/pages/email-signup/email-signup.ts +++ b/src/core/login/pages/email-signup/email-signup.ts @@ -229,7 +229,8 @@ export class CoreLoginEmailSignupPage { } // Get the data for the custom profile fields. - this.userProfileFieldDelegate.getDataForFields(this.settings.profilefields, true, 'email', this.signupForm.value).then((fieldsData) => { + this.userProfileFieldDelegate.getDataForFields(this.settings.profilefields, true, 'email', this.signupForm.value).then( + (fieldsData) => { params.customprofilefields = fieldsData; this.wsProvider.callAjax('auth_email_signup_user', params, {siteUrl: this.siteUrl}).then((result) => { diff --git a/src/core/mainmenu/providers/delegate.ts b/src/core/mainmenu/providers/delegate.ts index da5594cd5..c2a4e3959 100644 --- a/src/core/mainmenu/providers/delegate.ts +++ b/src/core/mainmenu/providers/delegate.ts @@ -88,7 +88,7 @@ export class CoreMainMenuDelegate extends CoreDelegate { protected handlers: {[s: string]: CoreMainMenuHandler} = {}; protected enabledHandlers: {[s: string]: CoreMainMenuHandler} = {}; protected loaded = false; - protected siteHandlers: Subject = new BehaviorSubject([]); + protected siteHandlers: Subject = new BehaviorSubject([]); protected featurePrefix = '$mmSideMenuDelegate_'; constructor(protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, diff --git a/src/core/sitehome/components/all-course-list/all-course-list.ts b/src/core/sitehome/components/all-course-list/all-course-list.ts index 71ea79d22..3e71c3373 100644 --- a/src/core/sitehome/components/all-course-list/all-course-list.ts +++ b/src/core/sitehome/components/all-course-list/all-course-list.ts @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input } from '@angular/core'; -import { IonicPage } from 'ionic-angular'; +import { Component } from '@angular/core'; import { CoreCoursesProvider } from '../../../courses/providers/courses'; /** diff --git a/src/core/sitehome/components/categories/categories.ts b/src/core/sitehome/components/categories/categories.ts index 6cfa1b25d..3ae52da32 100644 --- a/src/core/sitehome/components/categories/categories.ts +++ b/src/core/sitehome/components/categories/categories.ts @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input } from '@angular/core'; -import { IonicPage } from 'ionic-angular'; +import { Component } from '@angular/core'; import { CoreCoursesProvider } from '../../../courses/providers/courses'; /** diff --git a/src/core/sitehome/components/course-search/course-search.ts b/src/core/sitehome/components/course-search/course-search.ts index 8a74bfad5..454a3d1f4 100644 --- a/src/core/sitehome/components/course-search/course-search.ts +++ b/src/core/sitehome/components/course-search/course-search.ts @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input } from '@angular/core'; -import { IonicPage } from 'ionic-angular'; +import { Component } from '@angular/core'; import { CoreCoursesProvider } from '../../../courses/providers/courses'; /** diff --git a/src/core/sitehome/components/enrolled-course-list/enrolled-course-list.ts b/src/core/sitehome/components/enrolled-course-list/enrolled-course-list.ts index ab05eeb73..5c3cf6ae2 100644 --- a/src/core/sitehome/components/enrolled-course-list/enrolled-course-list.ts +++ b/src/core/sitehome/components/enrolled-course-list/enrolled-course-list.ts @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, OnInit } from '@angular/core'; -import { IonicPage } from 'ionic-angular'; +import { Component, OnInit } from '@angular/core'; import { CoreCoursesProvider } from '../../../courses/providers/courses'; /** diff --git a/src/core/sitehome/components/index/index.ts b/src/core/sitehome/components/index/index.ts index 88d052376..f883ab7dc 100644 --- a/src/core/sitehome/components/index/index.ts +++ b/src/core/sitehome/components/index/index.ts @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, Input } from '@angular/core'; -import { IonicPage } from 'ionic-angular'; +import { Component, OnInit } from '@angular/core'; import { CoreSitesProvider } from '../../../../providers/sites'; import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; import { CoreCourseProvider } from '../../../course/providers/course'; diff --git a/src/core/sitehome/components/news/news.ts b/src/core/sitehome/components/news/news.ts index 32519d767..a9b6ec75f 100644 --- a/src/core/sitehome/components/news/news.ts +++ b/src/core/sitehome/components/news/news.ts @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, Input } from '@angular/core'; -import { IonicPage } from 'ionic-angular'; +import { Component, OnInit } from '@angular/core'; import { CoreSitesProvider } from '../../../../providers/sites'; /** diff --git a/src/core/user/components/user-profile-field/user-profile-field.ts b/src/core/user/components/user-profile-field/user-profile-field.ts index b79bdfd0f..f10494844 100644 --- a/src/core/user/components/user-profile-field/user-profile-field.ts +++ b/src/core/user/components/user-profile-field/user-profile-field.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentRef, OnInit } from '@angular/core'; +import { Component, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, OnInit } from '@angular/core'; import { CoreLoggerProvider } from '../../../../providers/logger'; import { CoreUserProfileFieldDelegate } from '../../providers/user-profile-field-delegate'; import { CoreUtilsProvider } from '../../../../providers/utils/utils'; diff --git a/src/core/user/pages/about/about.html b/src/core/user/pages/about/about.html index f95e6f7c3..f56825db7 100644 --- a/src/core/user/pages/about/about.html +++ b/src/core/user/pages/about/about.html @@ -7,7 +7,7 @@ - +
{{ 'core.user.contact' | translate}} @@ -20,13 +20,13 @@

{{ 'core.user.phone1' | translate}}

- +

{{ 'core.user.phone2' | translate}}

- +

@@ -36,7 +36,8 @@ -

+ +

{{ 'core.user.city' | translate}}

@@ -52,7 +53,7 @@

{{ 'core.user.webpage' | translate}}

- +

diff --git a/src/core/user/pages/about/about.ts b/src/core/user/pages/about/about.ts index 603e8e43b..05c05926d 100644 --- a/src/core/user/pages/about/about.ts +++ b/src/core/user/pages/about/about.ts @@ -17,7 +17,6 @@ import { IonicPage, NavParams, Platform } from 'ionic-angular'; import { CoreUserProvider } from '../../providers/user'; import { CoreUserHelperProvider } from '../../providers/helper'; import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; -import { CoreCoursesProvider } from '../../../courses/providers/courses'; import { CoreEventsProvider } from '../../../../providers/events'; import { CoreSitesProvider } from '../../../../providers/sites'; @@ -41,7 +40,7 @@ export class CoreUserAboutPage { user: any = {}; title: string; - constructor(private navParams: NavParams, private userProvider: CoreUserProvider, private userHelper: CoreUserHelperProvider, + constructor(navParams: NavParams, private userProvider: CoreUserProvider, private userHelper: CoreUserHelperProvider, private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private platform: Platform) { diff --git a/src/core/user/pages/profile/profile.html b/src/core/user/pages/profile/profile.html index efc7cba54..fda94e955 100644 --- a/src/core/user/pages/profile/profile.html +++ b/src/core/user/pages/profile/profile.html @@ -7,7 +7,7 @@ - +
@@ -51,7 +51,7 @@ -