MOBILE-4059 mainmenu: Add link to contact support
This commit is contained in:
		
							parent
							
								
									95e0640eb2
								
							
						
					
					
						commit
						4eb01a063c
					
				| @ -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", | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
| @ -66,6 +66,14 @@ | ||||
|                     <p class="item-heading">{{ 'core.settings.preferences' | translate }}</p> | ||||
|                 </ion-label> | ||||
|             </ion-item> | ||||
| 
 | ||||
|             <ion-item *ngIf="displayContactSupport" button (click)="contactSupport($event)" | ||||
|                 [attr.aria-label]="'core.user.support' | translate" detail="true" detailIcon="open-outline" class="core-user-menu-support"> | ||||
|                 <ion-icon name="fas-envelope" slot="start" aria-hidden="true"></ion-icon> | ||||
|                 <ion-label> | ||||
|                     <p class="item-heading">{{ 'core.user.support' | translate }}</p> | ||||
|                 </ion-label> | ||||
|             </ion-item> | ||||
|         </ion-list> | ||||
|     </core-loading> | ||||
| </ion-content> | ||||
|  | ||||
| @ -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<void> { | ||||
|         await this.close(event); | ||||
|         await CoreUserSupport.contact(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Logout the user. | ||||
|      * | ||||
|  | ||||
| @ -28,6 +28,7 @@ | ||||
|     "roles": "Roles", | ||||
|     "sendemail": "Email", | ||||
|     "student": "Student", | ||||
|     "support": "Support", | ||||
|     "teacher": "Non-editing teacher", | ||||
|     "userwithid": "User with ID {{id}}", | ||||
|     "webpage": "Web page" | ||||
|  | ||||
							
								
								
									
										112
									
								
								src/core/features/user/services/support.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/core/features/user/services/support.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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<void> { | ||||
|         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; | ||||
| } | ||||
							
								
								
									
										13
									
								
								src/core/features/user/tests/behat/support-311.feature
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/core/features/user/tests/behat/support-311.feature
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
							
								
								
									
										33
									
								
								src/core/features/user/tests/behat/support.feature
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/core/features/user/tests/behat/support.feature
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| @ -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.
 | ||||
|  | ||||
| @ -282,6 +282,15 @@ export class CoreEvents { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Wait until an event has been emitted. | ||||
|      * | ||||
|      * @param eventName Event name. | ||||
|      */ | ||||
|     static waitUntil(eventName: string): Promise<void> { | ||||
|         return new Promise(resolve => this.once(eventName, () => resolve())); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user