MOBILE-4059 core: Encapsulate support config

main
Noel De Martin 2022-10-19 13:20:59 +02:00
parent b933c92f69
commit c4952133f1
22 changed files with 303 additions and 179 deletions

View File

@ -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;
};

View File

@ -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',

View File

@ -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<void> {
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,
});
}

View File

@ -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'),
}),
});

View File

@ -33,7 +33,7 @@
<p class="core-siteurl">{{siteUrl}}</p>
</div>
<core-login-exceeded-attempts *ngIf="loginAttempts >= 3" [siteConfig]="siteConfig" [siteUrl]="siteUrl"
<core-login-exceeded-attempts *ngIf="supportConfig && loginAttempts >= 3" [supportConfig]="supportConfig"
[supportSubject]="'core.login.exceededloginattemptssupportsubject' | translate">
{{ 'core.login.exceededloginattempts' | translate }}
</core-login-exceeded-attempts>

View File

@ -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<void> {
try {
this.siteUrl = CoreNavigator.getRequiredRouteParam<string>('siteUrl');
this.siteName = CoreNavigator.getRouteParam('siteName');
this.logoUrl = !CoreConstants.CONFIG.forceLoginLogo && CoreNavigator.getRouteParam('logoUrl') || undefined;
this.siteConfig = CoreNavigator.getRouteParam('siteConfig');
this.siteConfig = CoreNavigator.getRouteParam<CoreSitePublicConfigResponse>('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<void> {
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 });
}
/**

View File

@ -11,7 +11,7 @@
</ion-header>
<ion-content>
<div class="list-item-limited-width">
<core-login-exceeded-attempts *ngIf="wasPasswordResetRequestedRecently" [siteConfig]="siteConfig" [siteUrl]="siteUrl"
<core-login-exceeded-attempts *ngIf="supportConfig && wasPasswordResetRequestedRecently" [supportConfig]="supportConfig"
[supportSubject]="'core.login.exceededpasswordresetattemptssupportsubject' | translate">
{{ 'core.login.exceededpasswordresetattempts' | translate }}
</core-login-exceeded-attempts>

View File

@ -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<CoreSitePublicConfigResponse>('siteConfig');
this.siteUrl = siteUrl;
this.siteConfig = CoreNavigator.getRouteParam<CoreSitePublicConfigResponse>('siteConfig');
this.autoFocus = CorePlatform.is('tablet');
this.myForm = this.formBuilder.group({
field: ['username', Validators.required],
value: [CoreNavigator.getRouteParam<string>('username') || '', Validators.required],
});
this.supportConfig = siteConfig && new CoreUserGuestSupportConfig(siteConfig);
this.wasPasswordResetRequestedRecently = await CoreLoginHelper.wasPasswordResetRequestedRecently(siteUrl);
}

View File

@ -43,7 +43,7 @@
</ion-item>
</ion-card>
<core-login-exceeded-attempts *ngIf="reconnectAttempts >= 3" [siteConfig]="siteConfig" [siteUrl]="siteUrl"
<core-login-exceeded-attempts *ngIf="supportConfig && reconnectAttempts >= 3" [supportConfig]="supportConfig"
[supportSubject]="'core.login.exceededloginattemptssupportsubject' | translate">
{{ 'core.login.exceededloginattempts' | translate }}
</core-login-exceeded-attempts>

View File

@ -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<void> {
const supportPageUrl = this.siteConfig && CoreUserSupport.getSupportPageUrl(this.siteConfig);
await CoreUserSupport.contact({ supportPageUrl });
await CoreUserSupport.contact({ supportConfig: this.supportConfig });
}
/**

View File

@ -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<void> {
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 += '<div class="core-error-info-container"></div>';
}
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}`,
}),

View File

@ -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);

View File

@ -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`;
}
}

View File

@ -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<CoreUserSupportConfig> {
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`;
}
}

View File

@ -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');
}
}

View File

@ -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;
}

View File

@ -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'),
}),
});

View File

@ -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<void> {
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;
}

View File

@ -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',

View File

@ -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<CoreSiteSchema[]>('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<CoreLoginErrorOptions>): CoreLoginError;
protected createCannotConnectLoginError(siteUrl: string, options?: Partial<CoreLoginErrorOptions>): Promise<CoreLoginError>;
protected createCannotConnectLoginError(
siteUrlOrOptions: string | Partial<CoreLoginErrorOptions> = {},
options: Partial<CoreLoginErrorOptions> = {},
): CoreLoginError | Promise<CoreLoginError> {
const createError = (options: Partial<CoreLoginErrorOptions>) => new CoreLoginError({
protected createCannotConnectLoginError(options?: Partial<CoreLoginErrorOptions>): 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,
});

View File

@ -1366,11 +1366,12 @@ export class CoreDomUtilsProvider {
alertOptions.message += '<div class="core-error-info-container"></div>';
}
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}`,
}),

View File

@ -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<CoreSiteErrorOptions>,
): Promise<CoreSiteError> {
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,
});
}