From c4952133f18e7c89d478a2927c7c0f4802c6aa18 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 19 Oct 2022 13:20:59 +0200 Subject: [PATCH] MOBILE-4059 core: Encapsulate support config --- src/core/classes/errors/siteerror.ts | 46 +++---------- src/core/classes/site.ts | 35 +--------- .../exceeded-attempts/exceeded-attempts.ts | 13 ++-- .../pages/change-password/change-password.ts | 7 +- .../login/pages/credentials/credentials.html | 2 +- .../login/pages/credentials/credentials.ts | 17 +++-- .../forgotten-password.html | 2 +- .../forgotten-password/forgotten-password.ts | 8 ++- .../login/pages/reconnect/reconnect.html | 2 +- .../login/pages/reconnect/reconnect.ts | 12 ++-- src/core/features/login/pages/site/site.ts | 13 ++-- .../components/user-menu/user-menu.ts | 3 +- .../support/authenticated-support-config.ts | 57 ++++++++++++++++ .../classes/support/guest-support-config.ts | 65 +++++++++++++++++++ .../classes/support/null-support-config.ts | 39 +++++++++++ .../user/classes/support/support-config.ts | 47 ++++++++++++++ .../complete-profile/complete-profile.ts | 7 +- src/core/features/user/services/support.ts | 37 ++--------- .../initializers/prepare-inapp-browser.ts | 7 +- src/core/services/sites.ts | 42 +++++------- src/core/services/utils/dom.ts | 5 +- src/core/services/ws.ts | 16 ++--- 22 files changed, 303 insertions(+), 179 deletions(-) create mode 100644 src/core/features/user/classes/support/authenticated-support-config.ts create mode 100644 src/core/features/user/classes/support/guest-support-config.ts create mode 100644 src/core/features/user/classes/support/null-support-config.ts create mode 100644 src/core/features/user/classes/support/support-config.ts diff --git a/src/core/classes/errors/siteerror.ts b/src/core/classes/errors/siteerror.ts index cbb441178..4b5e7035c 100644 --- a/src/core/classes/errors/siteerror.ts +++ b/src/core/classes/errors/siteerror.ts @@ -13,8 +13,7 @@ // limitations under the License. import { CoreError } from '@classes/errors/error'; -import { CoreSitePublicConfigResponse } from '@classes/site'; -import { CoreUserSupport } from '@features/user/services/support'; +import { CoreUserSupportConfig } from '@features/user/classes/support/support-config'; /** * Error returned when performing operations regarding a site. @@ -23,42 +22,14 @@ export class CoreSiteError extends CoreError { errorcode?: string; errorDetails?: string; - contactSupport?: boolean; - siteConfig?: CoreSitePublicConfigResponse; + supportConfig?: CoreUserSupportConfig; constructor(options: CoreSiteErrorOptions) { super(getErrorMessage(options)); this.errorcode = options.errorcode; this.errorDetails = options.errorDetails; - this.contactSupport = options.contactSupport; - this.siteConfig = options.siteConfig; - } - - /** - * Get a url to contact site support. - * - * @returns Support page url. - */ - getSupportPageUrl(): string { - if (!this.siteConfig) { - throw new CoreError('Can\'t get support page url'); - } - - return CoreUserSupport.getSupportPageUrl(this.siteConfig); - } - - /** - * Check whether the handling of this error allows users to contact support or not. - * - * @returns Whether to contact support or not. - */ - canContactSupport(): boolean { - if (!this.contactSupport || !this.siteConfig) { - return false; - } - - return CoreUserSupport.canContactSupport(this.siteConfig); + this.supportConfig = options.supportConfig; } } @@ -70,10 +41,7 @@ export class CoreSiteError extends CoreError { * @returns Error message. */ function getErrorMessage(options: CoreSiteErrorOptions): string { - if ( - options.contactSupport && - (!options.siteConfig || !CoreUserSupport.canContactSupport(options.siteConfig)) - ) { + if ('supportConfig' in options && !options.supportConfig?.canContactSupport()) { return options.fallbackMessage ?? options.message; } @@ -85,6 +53,8 @@ export type CoreSiteErrorOptions = { fallbackMessage?: string; // Message to use when contacting support is not possible but warranted. errorcode?: string; // Technical error code useful for technical assistance. errorDetails?: string; // Technical error details useful for technical assistance. - contactSupport?: boolean; // Whether this error warrants contacting site support or not. - siteConfig?: CoreSitePublicConfigResponse; + + // Configuration to use to contact site support. If this attribute is present, it means + // that the error warrants contacting support. + supportConfig?: CoreUserSupportConfig; }; diff --git a/src/core/classes/site.ts b/src/core/classes/site.ts index 699af83c1..cd0fec4c7 100644 --- a/src/core/classes/site.ts +++ b/src/core/classes/site.ts @@ -60,8 +60,8 @@ import { import { Observable, ObservableInput, ObservedValueOf, OperatorFunction, Subject } from 'rxjs'; import { finalize, map, mergeMap } from 'rxjs/operators'; import { firstValueFrom } from '../utils/rxjs'; -import { CoreUserSupport } from '@features/user/services/support'; import { CoreSiteError } from '@classes/errors/siteerror'; +import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config'; /** * QR Code type enumeration. @@ -264,19 +264,6 @@ export class CoreSite { return this.db; } - /** - * Get url to contact site support. - * - * @returns Site support page url. - */ - getSupportPageUrl(): string | null { - if (!this.config || !this.canContactSupport()) { - return null; - } - - return CoreUserSupport.getSupportPageUrl(this.config, this.siteUrl); - } - /** * Get site user's ID. * @@ -436,19 +423,6 @@ export class CoreSite { return !!(info && (info.usercanmanageownfiles === undefined || info.usercanmanageownfiles)); } - /** - * Check whether this site has a support url available. - * - * @returns Whether this site has a support url. - */ - canContactSupport(): boolean { - if (this.isFeatureDisabled('NoDelegate_CoreUserSupport')) { - return false; - } - - return !!this.config && CoreUserSupport.canContactSupport(this.config); - } - /** * Can the user download files? * @@ -1158,13 +1132,8 @@ export class CoreSite { ); if (!data || !data.responses) { - const siteConfig = await CoreUtils.ignoreErrors( - this.getPublicConfig({ readingStrategy: CoreSitesReadingStrategy.ONLY_CACHE }), - ); - throw new CoreSiteError({ - siteConfig, - contactSupport: true, + supportConfig: new CoreUserAuthenticatedSupportConfig(this), message: Translate.instant('core.cannotconnecttrouble'), fallbackMessage: Translate.instant('core.cannotconnecttroublewithoutsupport'), errorcode: 'invalidresponse', diff --git a/src/core/features/login/components/exceeded-attempts/exceeded-attempts.ts b/src/core/features/login/components/exceeded-attempts/exceeded-attempts.ts index f1be67bd9..867dc4e45 100644 --- a/src/core/features/login/components/exceeded-attempts/exceeded-attempts.ts +++ b/src/core/features/login/components/exceeded-attempts/exceeded-attempts.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, Input, OnInit } from '@angular/core'; -import { CoreSiteConfig } from '@classes/site'; +import { CoreUserSupportConfig } from '@features/user/classes/support/support-config'; import { CoreUserSupport } from '@features/user/services/support'; @Component({ @@ -23,8 +23,7 @@ import { CoreUserSupport } from '@features/user/services/support'; }) export class CoreLoginExceededAttemptsComponent implements OnInit { - @Input() siteUrl!: string; - @Input() siteConfig!: CoreSiteConfig; + @Input() supportConfig!: CoreUserSupportConfig; @Input() supportSubject?: string; canContactSupport = false; @@ -33,19 +32,15 @@ export class CoreLoginExceededAttemptsComponent implements OnInit { * @inheritdoc */ ngOnInit(): void { - this.canContactSupport = CoreUserSupport.canContactSupport(this.siteConfig); + this.canContactSupport = this.supportConfig.canContactSupport(); } /** * Contact site support. */ async contactSupport(): Promise { - if (!this.siteConfig) { - throw new Error('Can\'t contact support without config'); - } - await CoreUserSupport.contact({ - supportPageUrl: CoreUserSupport.getSupportPageUrl(this.siteConfig, this.siteUrl), + supportConfig: this.supportConfig, subject: this.supportSubject, }); } diff --git a/src/core/features/login/pages/change-password/change-password.ts b/src/core/features/login/pages/change-password/change-password.ts index 8ba206d7f..84961975b 100644 --- a/src/core/features/login/pages/change-password/change-password.ts +++ b/src/core/features/login/pages/change-password/change-password.ts @@ -23,6 +23,7 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreUtils } from '@services/utils/utils'; import { CoreUserSupport } from '@features/user/services/support'; import { AlertButton } from '@ionic/angular'; +import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config'; /** * Page that shows instructions to change the password. @@ -48,14 +49,14 @@ export class CoreLoginChangePasswordPage implements OnDestroy { * Show a help modal. */ showHelp(): void { - const site = CoreSites.getRequiredCurrentSite(); + const supportConfig = CoreUserAuthenticatedSupportConfig.forCurrentSite(); const buttons: (AlertButton | string)[] = []; - if (site.canContactSupport()) { + if (supportConfig.canContactSupport()) { buttons.push({ text: Translate.instant('core.contactsupport'), handler: () => CoreUserSupport.contact({ - supportPageUrl: site.getSupportPageUrl(), + supportConfig, subject: Translate.instant('core.login.changepasswordsupportsubject'), }), }); diff --git a/src/core/features/login/pages/credentials/credentials.html b/src/core/features/login/pages/credentials/credentials.html index 40f69c9c6..3f101e383 100644 --- a/src/core/features/login/pages/credentials/credentials.html +++ b/src/core/features/login/pages/credentials/credentials.html @@ -33,7 +33,7 @@

{{siteUrl}}

- {{ 'core.login.exceededloginattempts' | translate }} diff --git a/src/core/features/login/pages/credentials/credentials.ts b/src/core/features/login/pages/credentials/credentials.ts index d414ad1d1..d9a278485 100644 --- a/src/core/features/login/pages/credentials/credentials.ts +++ b/src/core/features/login/pages/credentials/credentials.ts @@ -29,6 +29,8 @@ import { CoreEvents } from '@singletons/events'; import { CoreNavigator } from '@services/navigator'; import { CoreForms } from '@singletons/form'; import { CoreUserSupport } from '@features/user/services/support'; +import { CoreUserSupportConfig } from '@features/user/classes/support/support-config'; +import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config'; /** * Page to enter the user credentials. @@ -56,9 +58,10 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { showForgottenPassword = true; showScanQR = false; loginAttempts = 0; - siteConfig?: CoreSitePublicConfigResponse; + supportConfig?: CoreUserSupportConfig; canContactSupport?: boolean; + protected siteConfig?: CoreSitePublicConfigResponse; protected eventThrown = false; protected viewLeft = false; protected siteId?: string; @@ -75,12 +78,12 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { async ngOnInit(): Promise { try { this.siteUrl = CoreNavigator.getRequiredRouteParam('siteUrl'); - this.siteName = CoreNavigator.getRouteParam('siteName'); this.logoUrl = !CoreConstants.CONFIG.forceLoginLogo && CoreNavigator.getRouteParam('logoUrl') || undefined; - this.siteConfig = CoreNavigator.getRouteParam('siteConfig'); + this.siteConfig = CoreNavigator.getRouteParam('siteConfig'); this.urlToOpen = CoreNavigator.getRouteParam('urlToOpen'); - this.canContactSupport = this.siteConfig && CoreUserSupport.canContactSupport(this.siteConfig); + this.supportConfig = this.siteConfig && new CoreUserGuestSupportConfig(this.siteConfig); + this.canContactSupport = this.supportConfig?.canContactSupport(); } catch (error) { CoreDomUtils.showErrorModal(error); @@ -132,9 +135,11 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { * Contact site support. */ async contactSupport(): Promise { - const supportPageUrl = this.siteConfig && CoreUserSupport.getSupportPageUrl(this.siteConfig); + if (!this.supportConfig) { + throw new Error('can\'t contact support'); + } - await CoreUserSupport.contact({ supportPageUrl }); + await CoreUserSupport.contact({ supportConfig: this.supportConfig }); } /** diff --git a/src/core/features/login/pages/forgotten-password/forgotten-password.html b/src/core/features/login/pages/forgotten-password/forgotten-password.html index b079fbcd3..023840479 100644 --- a/src/core/features/login/pages/forgotten-password/forgotten-password.html +++ b/src/core/features/login/pages/forgotten-password/forgotten-password.html @@ -11,7 +11,7 @@
- {{ 'core.login.exceededpasswordresetattempts' | translate }} diff --git a/src/core/features/login/pages/forgotten-password/forgotten-password.ts b/src/core/features/login/pages/forgotten-password/forgotten-password.ts index e838f069d..2caaf696a 100644 --- a/src/core/features/login/pages/forgotten-password/forgotten-password.ts +++ b/src/core/features/login/pages/forgotten-password/forgotten-password.ts @@ -23,6 +23,8 @@ import { CoreNavigator } from '@services/navigator'; import { CoreForms } from '@singletons/form'; import { CorePlatform } from '@services/platform'; import { CoreSitePublicConfigResponse } from '@classes/site'; +import { CoreUserSupportConfig } from '@features/user/classes/support/support-config'; +import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config'; /** * Page to recover a forgotten password. @@ -37,8 +39,8 @@ export class CoreLoginForgottenPasswordPage implements OnInit { myForm!: FormGroup; siteUrl!: string; - siteConfig?: CoreSitePublicConfigResponse; autoFocus!: boolean; + supportConfig?: CoreUserSupportConfig; wasPasswordResetRequestedRecently = false; constructor(protected formBuilder: FormBuilder) {} @@ -55,14 +57,16 @@ export class CoreLoginForgottenPasswordPage implements OnInit { return; } + const siteConfig = CoreNavigator.getRouteParam('siteConfig'); + this.siteUrl = siteUrl; - this.siteConfig = CoreNavigator.getRouteParam('siteConfig'); this.autoFocus = CorePlatform.is('tablet'); this.myForm = this.formBuilder.group({ field: ['username', Validators.required], value: [CoreNavigator.getRouteParam('username') || '', Validators.required], }); + this.supportConfig = siteConfig && new CoreUserGuestSupportConfig(siteConfig); this.wasPasswordResetRequestedRecently = await CoreLoginHelper.wasPasswordResetRequestedRecently(siteUrl); } diff --git a/src/core/features/login/pages/reconnect/reconnect.html b/src/core/features/login/pages/reconnect/reconnect.html index 334f23769..905e12abe 100644 --- a/src/core/features/login/pages/reconnect/reconnect.html +++ b/src/core/features/login/pages/reconnect/reconnect.html @@ -43,7 +43,7 @@ - {{ 'core.login.exceededloginattempts' | translate }} diff --git a/src/core/features/login/pages/reconnect/reconnect.ts b/src/core/features/login/pages/reconnect/reconnect.ts index 1abb7448e..41ad23022 100644 --- a/src/core/features/login/pages/reconnect/reconnect.ts +++ b/src/core/features/login/pages/reconnect/reconnect.ts @@ -27,6 +27,8 @@ import { CoreError } from '@classes/errors/error'; import { CoreNavigator, CoreRedirectPayload } from '@services/navigator'; import { CoreForms } from '@singletons/form'; import { CoreUserSupport } from '@features/user/services/support'; +import { CoreUserSupportConfig } from '@features/user/classes/support/support-config'; +import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config'; /** * Page to enter the user password to reconnect to a site. @@ -57,9 +59,10 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { showScanQR = false; showLoading = true; reconnectAttempts = 0; - siteConfig?: CoreSitePublicConfigResponse; + supportConfig?: CoreUserSupportConfig; canContactSupport?: boolean; + protected siteConfig?: CoreSitePublicConfigResponse; protected viewLeft = false; protected eventThrown = false; protected redirectData?: CoreRedirectPayload; @@ -104,7 +107,8 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { this.userAvatar = site.infos.userpictureurl; this.siteUrl = site.infos.siteurl; this.siteName = site.getSiteName(); - this.canContactSupport = site.canContactSupport(); + this.supportConfig = new CoreUserAuthenticatedSupportConfig(site); + this.canContactSupport = this.supportConfig.canContactSupport(); // If login was OAuth we should only reach this page if the OAuth method ID has changed. this.isOAuth = site.isOAuth(); @@ -141,9 +145,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy { * Contact site support. */ async contactSupport(): Promise { - const supportPageUrl = this.siteConfig && CoreUserSupport.getSupportPageUrl(this.siteConfig); - - await CoreUserSupport.contact({ supportPageUrl }); + await CoreUserSupport.contact({ supportConfig: this.supportConfig }); } /** diff --git a/src/core/features/login/pages/site/site.ts b/src/core/features/login/pages/site/site.ts index b965e4bf6..f4fa16765 100644 --- a/src/core/features/login/pages/site/site.ts +++ b/src/core/features/login/pages/site/site.ts @@ -43,6 +43,8 @@ import { AlertButton } from '@ionic/core'; import { CoreSiteError } from '@classes/errors/siteerror'; import { CoreUserSupport } from '@features/user/services/support'; import { CoreErrorInfoComponent } from '@components/error-info/error-info'; +import { CoreUserSupportConfig } from '@features/user/classes/support/support-config'; +import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config'; /** * Site (url) chooser when adding a new site. @@ -386,15 +388,15 @@ export class CoreLoginSitePage implements OnInit { protected async showLoginIssue(url: string | null, error: CoreError): Promise { let errorMessage = CoreDomUtils.getErrorMessage(error); let siteExists = false; - let supportPageUrl: string | null = null; + let supportConfig: CoreUserSupportConfig | undefined = undefined; let errorDetails: string | undefined; let errorCode: string | undefined; if (error instanceof CoreSiteError) { - siteExists = !!error.siteConfig; - supportPageUrl = error.canContactSupport() ? error.getSupportPageUrl() : null; + supportConfig = error.supportConfig; errorDetails = error.errorDetails; errorCode = error.errorcode; + siteExists = supportConfig instanceof CoreUserGuestSupportConfig; } if ( @@ -420,12 +422,13 @@ export class CoreLoginSitePage implements OnInit { errorMessage += '
'; } + const alertSupportConfig = supportConfig; const buttons: AlertButton[] = [ - supportPageUrl + alertSupportConfig ? { text: Translate.instant('core.contactsupport'), handler: () => CoreUserSupport.contact({ - supportPageUrl, + supportConfig: alertSupportConfig, subject: Translate.instant('core.cannotconnect', { $a: CoreSite.MINIMUM_MOODLE_VERSION }), message: `Error: ${errorCode}\n\n${errorDetails}`, }), diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.ts b/src/core/features/mainmenu/components/user-menu/user-menu.ts index c58468c3d..b94d145aa 100644 --- a/src/core/features/mainmenu/components/user-menu/user-menu.ts +++ b/src/core/features/mainmenu/components/user-menu/user-menu.ts @@ -18,6 +18,7 @@ import { CoreSite, CoreSiteInfo } from '@classes/site'; import { CoreFilter } from '@features/filter/services/filter'; import { CoreLoginSitesComponent } from '@features/login/components/sites/sites'; import { CoreLoginHelper } from '@features/login/services/login-helper'; +import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config'; import { CoreUserSupport } from '@features/user/services/support'; import { CoreUser, CoreUserProfile } from '@features/user/services/user'; import { @@ -67,7 +68,7 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { this.siteName = currentSite.getSiteName(); this.siteUrl = currentSite.getURL(); this.displaySwitchAccount = !currentSite.isFeatureDisabled('NoDelegate_SwitchAccount'); - this.displayContactSupport = currentSite.canContactSupport(); + this.displayContactSupport = new CoreUserAuthenticatedSupportConfig(currentSite).canContactSupport(); this.removeAccountOnLogout = !!CoreConstants.CONFIG.removeaccountonlogout; this.loadSiteLogo(currentSite); diff --git a/src/core/features/user/classes/support/authenticated-support-config.ts b/src/core/features/user/classes/support/authenticated-support-config.ts new file mode 100644 index 000000000..509b30c9f --- /dev/null +++ b/src/core/features/user/classes/support/authenticated-support-config.ts @@ -0,0 +1,57 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreSite } from '@classes/site'; +import { CoreSites } from '@services/sites'; +import { CoreUserSupportConfig } from './support-config'; + +/** + * Support config for an authenticated user. + */ +export class CoreUserAuthenticatedSupportConfig extends CoreUserSupportConfig { + + /** + * Get config for the current site. + * + * @returns Support config. + */ + static forCurrentSite(): CoreUserAuthenticatedSupportConfig { + return new CoreUserAuthenticatedSupportConfig(CoreSites.getRequiredCurrentSite()); + } + + private site: CoreSite; + + constructor(site: CoreSite) { + super(); + + this.site = site; + } + + /** + * @inheritdoc + */ + canContactSupport(): boolean { + return this.site.isVersionGreaterEqualThan('4.0') + && !this.site.isFeatureDisabled('NoDelegate_CoreUserSupport'); + } + + /** + * @inheritdoc + */ + protected buildSupportPageUrl(): string { + return this.site.config?.supportpage?.trim() + || `${this.site.config?.httpswwwroot ?? this.site.config?.wwwroot ?? this.site.siteUrl}/user/contactsitesupport.php`; + } + +} diff --git a/src/core/features/user/classes/support/guest-support-config.ts b/src/core/features/user/classes/support/guest-support-config.ts new file mode 100644 index 000000000..e48af2ee0 --- /dev/null +++ b/src/core/features/user/classes/support/guest-support-config.ts @@ -0,0 +1,65 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreSitePublicConfigResponse } from '@classes/site'; +import { CoreUserNullSupportConfig } from '@features/user/classes/support/null-support-config'; +import { CoreSites } from '@services/sites'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreUserSupportConfig } from './support-config'; + +/** + * Support config for a guest user. + */ +export class CoreUserGuestSupportConfig extends CoreUserSupportConfig { + + /** + * Get support config for a site with given url. + * + * @param siteUrl Site url. + * @returns Support config. + */ + static async forSite(siteUrl: string): Promise { + const siteConfig = await CoreUtils.ignoreErrors(CoreSites.getPublicSiteConfigByUrl(siteUrl)); + + if (!siteConfig) { + return new CoreUserNullSupportConfig(); + } + + return new CoreUserGuestSupportConfig(siteConfig); + } + + private config: CoreSitePublicConfigResponse; + + constructor(config: CoreSitePublicConfigResponse) { + super(); + + this.config = config; + } + + /** + * @inheritdoc + */ + canContactSupport(): boolean { + return 'supportpage' in this.config; + } + + /** + * @inheritdoc + */ + protected buildSupportPageUrl(): string { + return this.config.supportpage?.trim() + || `${this.config.httpswwwroot || this.config.wwwroot}/user/contactsitesupport.php`; + } + +} diff --git a/src/core/features/user/classes/support/null-support-config.ts b/src/core/features/user/classes/support/null-support-config.ts new file mode 100644 index 000000000..cfdc6d5c4 --- /dev/null +++ b/src/core/features/user/classes/support/null-support-config.ts @@ -0,0 +1,39 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreUserSupportConfig } from './support-config'; + +/** + * Null representation for a support config object. + * + * This class can be used in place of a functional support config when it's hasn't been possible + * to obtain any site configuration to extract information about support. + */ +export class CoreUserNullSupportConfig extends CoreUserSupportConfig { + + /** + * @inheritdoc + */ + canContactSupport(): boolean { + return false; + } + + /** + * @inheritdoc + */ + protected buildSupportPageUrl(): string { + throw new Error('Can\'t build a support page url from a null config'); + } + +} diff --git a/src/core/features/user/classes/support/support-config.ts b/src/core/features/user/classes/support/support-config.ts new file mode 100644 index 000000000..093d6069e --- /dev/null +++ b/src/core/features/user/classes/support/support-config.ts @@ -0,0 +1,47 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Encapsulates the support affordances a user has access to. + */ +export abstract class CoreUserSupportConfig { + + /** + * Check whether the user can contact support or not. + * + * @return Whether the user can contact support. + */ + public abstract canContactSupport(): boolean; + + /** + * Get url to use for contacting support. + * + * @returns Support page url. + */ + getSupportPageUrl(): string { + if (!this.canContactSupport()) { + throw new Error('Can\'t get support page url'); + } + + return this.buildSupportPageUrl(); + } + + /** + * Build page url string with the internal information. + * + * @return Support page url. + */ + protected abstract buildSupportPageUrl(): string; + +} diff --git a/src/core/features/user/pages/complete-profile/complete-profile.ts b/src/core/features/user/pages/complete-profile/complete-profile.ts index 33a14939f..28042e741 100644 --- a/src/core/features/user/pages/complete-profile/complete-profile.ts +++ b/src/core/features/user/pages/complete-profile/complete-profile.ts @@ -23,6 +23,7 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreUtils } from '@services/utils/utils'; import { AlertButton } from '@ionic/angular'; import { CoreUserSupport } from '@features/user/services/support'; +import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config'; /** * Page that shows instructions to complete the profile. @@ -47,14 +48,14 @@ export class CoreUserCompleteProfilePage implements OnDestroy { * Show a help modal. */ showHelp(): void { - const site = CoreSites.getRequiredCurrentSite(); + const supportConfig = CoreUserAuthenticatedSupportConfig.forCurrentSite(); const buttons: (AlertButton | string)[] = []; - if (site.canContactSupport()) { + if (supportConfig.canContactSupport()) { buttons.push({ text: Translate.instant('core.contactsupport'), handler: () => CoreUserSupport.contact({ - supportPageUrl: site.getSupportPageUrl(), + supportConfig, subject: Translate.instant('core.login.completeprofilesupportsubject'), }), }); diff --git a/src/core/features/user/services/support.ts b/src/core/features/user/services/support.ts index 2d59772ea..e295caaeb 100644 --- a/src/core/features/user/services/support.ts +++ b/src/core/features/user/services/support.ts @@ -13,8 +13,8 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreError } from '@classes/errors/error'; -import { CoreSiteConfig, CoreSitePublicConfigResponse } from '@classes/site'; +import { CoreUserSupportConfig } from '@features/user/classes/support/support-config'; +import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config'; import { InAppBrowserObject } from '@ionic-native/in-app-browser'; import { CorePlatform } from '@services/platform'; import { CoreSites } from '@services/sites'; @@ -35,12 +35,8 @@ export class CoreUserSupportService { * @param options Options to configure the interaction with support. */ async contact(options: CoreUserSupportContactOptions = {}): Promise { - const supportPageUrl = options.supportPageUrl ?? CoreSites.getRequiredCurrentSite().getSupportPageUrl(); - - if (!supportPageUrl) { - throw new CoreError('Could not get support url'); - } - + const supportConfig = options.supportConfig ?? CoreUserAuthenticatedSupportConfig.forCurrentSite(); + const supportPageUrl = supportConfig.getSupportPageUrl(); const autoLoginUrl = await CoreSites.getCurrentSite()?.getAutoLoginUrl(supportPageUrl, false); const browser = CoreUtils.openInApp(autoLoginUrl ?? supportPageUrl); @@ -51,29 +47,6 @@ export class CoreUserSupportService { await CoreEvents.waitUntil(CoreEvents.IAB_EXIT); } - /** - * Get support page url from site config. - * - * @param config Site config. - * @returns Support page url. - */ - getSupportPageUrl(config: CoreSitePublicConfigResponse): string; - getSupportPageUrl(config: CoreSiteConfig, siteUrl: string): string; - getSupportPageUrl(config: CoreSiteConfig | CoreSitePublicConfigResponse, siteUrl?: string): string { - return config.supportpage?.trim() - || `${config.httpswwwroot ?? config.wwwroot ?? siteUrl}/user/contactsitesupport.php`; - } - - /** - * Check whether a site config allows contacting support. - * - * @param config Site config. - * @returns Whether site support can be contacted. - */ - canContactSupport(config: CoreSiteConfig | CoreSitePublicConfigResponse): boolean { - return 'supportpage' in config; - } - /** * Inject error details into contact support form. * @@ -106,7 +79,7 @@ export const CoreUserSupport = makeSingleton(CoreUserSupportService); * Options to configure interaction with support. */ export interface CoreUserSupportContactOptions { - supportPageUrl?: string | null; + supportConfig?: CoreUserSupportConfig | null; subject?: string | null; message?: string | null; } diff --git a/src/core/initializers/prepare-inapp-browser.ts b/src/core/initializers/prepare-inapp-browser.ts index 61a717249..f9631b9ed 100644 --- a/src/core/initializers/prepare-inapp-browser.ts +++ b/src/core/initializers/prepare-inapp-browser.ts @@ -14,7 +14,10 @@ import { CoreSiteError } from '@classes/errors/siteerror'; import { CoreLoginHelper } from '@features/login/services/login-helper'; +import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config'; +import { CoreUserNullSupportConfig } from '@features/user/classes/support/null-support-config'; import { CoreApp } from '@services/app'; +import { CoreSites } from '@services/sites'; import { CoreCustomURLSchemes } from '@services/urlschemes'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUrlUtils } from '@services/utils/url'; @@ -47,7 +50,9 @@ export default function(): void { // It's an SSO token for another app. Close the IAB and show an error. CoreUtils.closeInAppBrowser(); CoreDomUtils.showErrorModal(new CoreSiteError({ - contactSupport: true, + supportConfig: CoreSites.getCurrentSite() + ? CoreUserAuthenticatedSupportConfig.forCurrentSite() + : new CoreUserNullSupportConfig(), message: Translate.instant('core.cannotconnecttrouble'), fallbackMessage: Translate.instant('core.cannotconnecttroublewithoutsupport'), errorcode: 'invalidurlscheme', diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index 432fb8fa2..8672928d7 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -61,6 +61,7 @@ import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/da import { asyncInstance, AsyncInstance } from '../utils/async-instance'; import { CoreConfig } from './config'; import { CoreNetwork } from '@services/network'; +import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config'; export const CORE_SITE_SCHEMAS = new InjectionToken('CORE_SITE_SCHEMAS'); export const CORE_SITE_CURRENT_SITE_ID_CONFIG = 'current_site_id'; @@ -297,16 +298,16 @@ export class CoreSitesProvider { // Check that the user can authenticate. if (!config.enablewebservices) { throw this.createCannotConnectLoginError({ + supportConfig: new CoreUserGuestSupportConfig(config), errorcode: 'webservicesnotenabled', errorDetails: Translate.instant('core.login.webservicesnotenabled'), - siteConfig: config, critical: true, }); } else if (!config.enablemobilewebservice) { throw this.createCannotConnectLoginError({ + supportConfig: new CoreUserGuestSupportConfig(config), errorcode: 'mobileservicesnotenabled', errorDetails: Translate.instant('core.login.mobileservicesnotenabled'), - siteConfig: config, critical: true, }); } else if (config.maintenanceenabled) { @@ -332,24 +333,12 @@ export class CoreSitesProvider { * @param options Error options. * @return Cannot connect error. */ - protected createCannotConnectLoginError(options?: Partial): CoreLoginError; - protected createCannotConnectLoginError(siteUrl: string, options?: Partial): Promise; - protected createCannotConnectLoginError( - siteUrlOrOptions: string | Partial = {}, - options: Partial = {}, - ): CoreLoginError | Promise { - const createError = (options: Partial) => new CoreLoginError({ + protected createCannotConnectLoginError(options?: Partial): CoreLoginError { + return new CoreLoginError({ ...options, message: Translate.instant('core.cannotconnecttrouble'), fallbackMessage: Translate.instant('core.cannotconnecttroublewithoutsupport'), - contactSupport: true, }); - - return typeof siteUrlOrOptions === 'object' - ? createError(siteUrlOrOptions) - : CoreUtils - .ignoreErrors(this.getPublicSiteConfigByUrl(siteUrlOrOptions)) - .then(siteConfig => createError({ ...options, siteConfig })); } /** @@ -376,8 +365,7 @@ export class CoreSitesProvider { critical: true, message: error.message, errorcode: error.errorcode, - contactSupport: error.contactSupport, - siteConfig: error.siteConfig, + supportConfig: error.supportConfig, errorDetails: error.errorDetails, }; @@ -425,7 +413,8 @@ export class CoreSitesProvider { data = await Http.post(siteUrl + '/login/token.php', { appsitecheck: 1 }).pipe(timeout(CoreWS.getRequestTimeout())) .toPromise(); } catch (error) { - throw await this.createCannotConnectLoginError(siteUrl, { + throw this.createCannotConnectLoginError({ + supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl), errorcode: 'sitecheckfailed', errorDetails: CoreDomUtils.getErrorMessage(error) ?? undefined, }); @@ -433,14 +422,16 @@ export class CoreSitesProvider { if (data === null) { // Cannot connect. - throw await this.createCannotConnectLoginError(siteUrl, { + throw this.createCannotConnectLoginError({ + supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl), errorcode: 'appsitecheckfailed', errorDetails: 'A request to /login/token.php with appsitecheck=1 returned an empty response', }); } if (data.errorcode && (data.errorcode == 'enablewsdescription' || data.errorcode == 'requirecorrectaccess')) { - throw await this.createCannotConnectLoginError(siteUrl, { + throw this.createCannotConnectLoginError({ + supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl), critical: data.errorcode == 'enablewsdescription', errorcode: data.errorcode, errorDetails: data.error, @@ -448,7 +439,8 @@ export class CoreSitesProvider { } if (data.error && data.error == 'Web services must be enabled in Advanced features.') { - throw await this.createCannotConnectLoginError(siteUrl, { + throw this.createCannotConnectLoginError({ + supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl), critical: true, errorcode: 'enablewsdescription', errorDetails: data.error, @@ -511,14 +503,16 @@ export class CoreSitesProvider { const redirect = await CoreUtils.checkRedirect(loginUrl); if (redirect) { - throw await this.createCannotConnectLoginError(siteUrl, { + throw this.createCannotConnectLoginError({ + supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl), errorcode: 'sitehasredirect', errorDetails: Translate.instant('core.login.sitehasredirect'), }); } } - throw await this.createCannotConnectLoginError(siteUrl, { + throw this.createCannotConnectLoginError({ + supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl), errorcode: data.errorcode, errorDetails: data.error, }); diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index da36d7068..2401f0704 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -1366,11 +1366,12 @@ export class CoreDomUtilsProvider { alertOptions.message += '
'; } - if (error.canContactSupport()) { + const supportConfig = error.supportConfig; + if (supportConfig?.canContactSupport()) { alertOptions.buttons.push({ text: Translate.instant('core.contactsupport'), handler: () => CoreUserSupport.contact({ - supportPageUrl: error.getSupportPageUrl(), + supportConfig, subject: alertOptions.header, message: `${error.errorcode}\n\n${error.errorDetails}`, }), diff --git a/src/core/services/ws.ts b/src/core/services/ws.ts index 3fdf8f021..adcfe5bdf 100644 --- a/src/core/services/ws.ts +++ b/src/core/services/ws.ts @@ -39,9 +39,8 @@ import { CoreSite } from '@classes/site'; import { CoreHttpError } from '@classes/errors/httperror'; import { CorePromisedValue } from '@classes/promised-value'; import { CorePlatform } from '@services/platform'; -import { CoreUtils } from '@services/utils/utils'; -import { CoreSites } from '@services/sites'; import { CoreSiteError, CoreSiteErrorOptions } from '@classes/errors/siteerror'; +import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config'; /** * This service allows performing WS calls and download/upload files. @@ -470,11 +469,8 @@ export class CoreWSProvider { // Check if error. Ajax layer should always return an object (if error) or an array (if success). if (!data || typeof data != 'object') { - const siteConfig = await CoreUtils.ignoreErrors(CoreSites.getPublicSiteConfigByUrl(preSets.siteUrl)); - throw new CoreAjaxError({ - siteConfig, - contactSupport: true, + supportConfig: await CoreUserGuestSupportConfig.forSite(preSets.siteUrl), message: Translate.instant('core.cannotconnecttrouble'), fallbackMessage: Translate.instant('core.cannotconnecttroublewithoutsupport'), errorcode: 'invalidresponse', @@ -496,8 +492,7 @@ export class CoreWSProvider { return data.data; }, async (data: HttpErrorResponse) => { const options: CoreSiteErrorOptions = { - contactSupport: true, - siteConfig: await CoreUtils.ignoreErrors(CoreSites.getPublicSiteConfigByUrl(preSets.siteUrl)), + supportConfig: await CoreUserGuestSupportConfig.forSite(preSets.siteUrl), message: Translate.instant('core.cannotconnecttrouble'), fallbackMessage: Translate.instant('core.cannotconnecttroublewithoutsupport'), }; @@ -1132,14 +1127,11 @@ export class CoreWSProvider { siteUrl: string, options?: Partial, ): Promise { - const siteConfig = await CoreUtils.ignoreErrors(CoreSites.getPublicSiteConfigByUrl(siteUrl)); - return new CoreSiteError({ ...options, - siteConfig, + supportConfig: await CoreUserGuestSupportConfig.forSite(siteUrl), message: Translate.instant('core.cannotconnecttrouble'), fallbackMessage: Translate.instant('core.cannotconnecttroublewithoutsupport'), - contactSupport: true, }); }