From 4eb01a063ce05d33e85165e41f9162a440781b09 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 21 Sep 2022 11:12:53 +0200 Subject: [PATCH] MOBILE-4059 mainmenu: Add link to contact support --- scripts/langindex.json | 1 + src/core/classes/site.ts | 28 +++++ .../components/user-menu/user-menu.html | 8 ++ .../components/user-menu/user-menu.ts | 13 ++ src/core/features/user/lang.json | 1 + src/core/features/user/services/support.ts | 112 ++++++++++++++++++ .../user/tests/behat/support-311.feature | 13 ++ .../features/user/tests/behat/support.feature | 33 ++++++ src/core/services/utils/utils.ts | 6 +- src/core/singletons/events.ts | 9 ++ 10 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 src/core/features/user/services/support.ts create mode 100644 src/core/features/user/tests/behat/support-311.feature create mode 100644 src/core/features/user/tests/behat/support.feature diff --git a/scripts/langindex.json b/scripts/langindex.json index 7cd0b2a57..fc60c508f 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -2363,6 +2363,7 @@ "core.user.roles": "moodle", "core.user.sendemail": "local_moodlemobileapp", "core.user.student": "moodle/defaultcoursestudent", + "core.user.support": "local_moodlemobileapp", "core.user.teacher": "moodle/noneditingteacher", "core.user.useraccount": "moodle", "core.user.userwithid": "local_moodlemobileapp", diff --git a/src/core/classes/site.ts b/src/core/classes/site.ts index 0af78f156..0c8ef1484 100644 --- a/src/core/classes/site.ts +++ b/src/core/classes/site.ts @@ -60,6 +60,7 @@ 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'; /** * QR Code type enumeration. @@ -262,6 +263,19 @@ 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. * @@ -421,6 +435,19 @@ 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? * @@ -2777,6 +2804,7 @@ export type CoreSitePublicConfigResponse = { agedigitalconsentverification?: boolean; // Whether age digital consent verification is enabled. supportname?: string; // Site support contact name (only if age verification is enabled). supportemail?: string; // Site support contact email (only if age verification is enabled). + supportpage?: string; // Site support contact url. autolang?: number; // Whether to detect default language from browser setting. lang?: string; // Default language for the site. langmenu?: number; // Whether the language menu should be displayed. diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.html b/src/core/features/mainmenu/components/user-menu/user-menu.html index d6e138a39..1533ef68d 100644 --- a/src/core/features/mainmenu/components/user-menu/user-menu.html +++ b/src/core/features/mainmenu/components/user-menu/user-menu.html @@ -66,6 +66,14 @@

{{ 'core.settings.preferences' | translate }}

+ + + + +

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

+
+
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 4b02fa22e..c58468c3d 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 { CoreUserSupport } from '@features/user/services/support'; import { CoreUser, CoreUserProfile } from '@features/user/services/user'; import { CoreUserProfileHandlerData, @@ -51,6 +52,7 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { handlersLoaded = false; user?: CoreUserProfile; displaySwitchAccount = true; + displayContactSupport = false; removeAccountOnLogout = false; protected subscription!: Subscription; @@ -65,6 +67,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.removeAccountOnLogout = !!CoreConstants.CONFIG.removeaccountonlogout; this.loadSiteLogo(currentSite); @@ -173,6 +176,16 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { handler.action(event, this.user, CoreUserDelegateContext.USER_MENU); } + /** + * Contact site support. + * + * @param event Click event. + */ + async contactSupport(event: Event): Promise { + await this.close(event); + await CoreUserSupport.contact(); + } + /** * Logout the user. * diff --git a/src/core/features/user/lang.json b/src/core/features/user/lang.json index 147b26797..c2b42e2d1 100644 --- a/src/core/features/user/lang.json +++ b/src/core/features/user/lang.json @@ -28,6 +28,7 @@ "roles": "Roles", "sendemail": "Email", "student": "Student", + "support": "Support", "teacher": "Non-editing teacher", "userwithid": "User with ID {{id}}", "webpage": "Web page" diff --git a/src/core/features/user/services/support.ts b/src/core/features/user/services/support.ts new file mode 100644 index 000000000..2d59772ea --- /dev/null +++ b/src/core/features/user/services/support.ts @@ -0,0 +1,112 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreError } from '@classes/errors/error'; +import { CoreSiteConfig, CoreSitePublicConfigResponse } from '@classes/site'; +import { InAppBrowserObject } from '@ionic-native/in-app-browser'; +import { CorePlatform } from '@services/platform'; +import { CoreSites } from '@services/sites'; +import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; +import { CoreEvents } from '@singletons/events'; +import { CoreSubscriptions } from '@singletons/subscriptions'; + +/** + * Handle site support. + */ +@Injectable({ providedIn: 'root' }) +export class CoreUserSupportService { + + /** + * Contact site support. + * + * @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 autoLoginUrl = await CoreSites.getCurrentSite()?.getAutoLoginUrl(supportPageUrl, false); + const browser = CoreUtils.openInApp(autoLoginUrl ?? supportPageUrl); + + if (supportPageUrl.endsWith('/user/contactsitesupport.php')) { + this.populateSupportForm(browser, options.subject, options.message); + } + + 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. + * + * @param browser In App browser containing the support form. + * @param subject Title to fill into the form. + * @param message Details to fill into the form. + */ + protected populateSupportForm(browser: InAppBrowserObject, subject?: string | null, message?: string | null): void { + if (!CorePlatform.isMobile()) { + return; + } + + const unsubscribe = CoreSubscriptions.once(browser.on('loadstop'), () => { + browser.executeScript({ + code: ` + document.querySelector('#id_subject').value = ${JSON.stringify(subject ?? '')}; + document.querySelector('#id_message').value = ${JSON.stringify(message ?? '')}; + `, + }); + }); + + CoreEvents.once(CoreEvents.IAB_EXIT, () => unsubscribe()); + } + +} + +export const CoreUserSupport = makeSingleton(CoreUserSupportService); + +/** + * Options to configure interaction with support. + */ +export interface CoreUserSupportContactOptions { + supportPageUrl?: string | null; + subject?: string | null; + message?: string | null; +} diff --git a/src/core/features/user/tests/behat/support-311.feature b/src/core/features/user/tests/behat/support-311.feature new file mode 100644 index 000000000..17277e00f --- /dev/null +++ b/src/core/features/user/tests/behat/support-311.feature @@ -0,0 +1,13 @@ +@core @core_user @app @javascript @lms_upto3.11 +Feature: Site support + + Background: + Given the following "users" exist: + | username | firstname | lastname | + | student1 | Student | Student | + + Scenario: Cannot contact support + Given I entered the app as "student1" + When I press the user menu button in the app + Then I should find "Blog entries" in the app + But I should not find "Support" in the app diff --git a/src/core/features/user/tests/behat/support.feature b/src/core/features/user/tests/behat/support.feature new file mode 100644 index 000000000..5f6166135 --- /dev/null +++ b/src/core/features/user/tests/behat/support.feature @@ -0,0 +1,33 @@ +@core @core_user @app @javascript @lms_from4.0 +Feature: Site support + + Background: + Given the following "users" exist: + | username | firstname | lastname | + | student1 | Student | Student | + + Scenario: Uses default support page + Given I entered the app as "student1" + When I press the user menu button in the app + Then I should find "Support" in the app + + When I press "Support" in the app + Then the app should have opened a browser tab with url ".*\/user\/contactsitesupport\.php" + + Scenario: Uses custom support page + Given the following config values are set as admin: + | supportpage | https://campus.example.edu/support | + And I entered the app as "student1" + When I press the user menu button in the app + Then I should find "Support" in the app + + When I press "Support" in the app + Then the app should have opened a browser tab with url "https:\/\/campus\.example\.edu\/support" + + Scenario: Cannot contact support + Given the following config values are set as admin: + | disabledfeatures | NoDelegate_CoreUserSupport | tool_mobile | + And I entered the app as "student1" + When I press the user menu button in the app + Then I should find "Blog entries" in the app + But I should not find "Support" in the app diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index 3ee795149..c241d3c2b 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -1044,11 +1044,7 @@ export class CoreUtilsProvider { * @param options Override default options passed to InAppBrowser. * @return The opened window. */ - openInApp(url: string, options?: InAppBrowserOptions): InAppBrowserObject | undefined { - if (!url) { - return; - } - + openInApp(url: string, options?: InAppBrowserOptions): InAppBrowserObject { options = options || {}; options.usewkwebview = 'yes'; // Force WKWebView in iOS. options.enableViewPortScale = options.enableViewPortScale ?? 'yes'; // Enable zoom on iOS by default. diff --git a/src/core/singletons/events.ts b/src/core/singletons/events.ts index 6d4d7213d..6dfdf5ea0 100644 --- a/src/core/singletons/events.ts +++ b/src/core/singletons/events.ts @@ -282,6 +282,15 @@ export class CoreEvents { } } + /** + * Wait until an event has been emitted. + * + * @param eventName Event name. + */ + static waitUntil(eventName: string): Promise { + return new Promise(resolve => this.once(eventName, () => resolve())); + } + } /**