From 9a0b86e3e32e99e0b2c57b2fb1c023569c7bab78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 14 Jul 2023 14:23:15 +0200 Subject: [PATCH 1/2] MOBILE-4009 enrol: Catch validation errors to cancel the prompt --- .../course-summary/course-summary.page.ts | 41 +++++++++++-------- .../features/course/services/course-helper.ts | 18 ++++++++ src/core/services/utils/dom.ts | 4 +- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/core/features/course/pages/course-summary/course-summary.page.ts b/src/core/features/course/pages/course-summary/course-summary.page.ts index 8185b598982..878c0d2a0f6 100644 --- a/src/core/features/course/pages/course-summary/course-summary.page.ts +++ b/src/core/features/course/pages/course-summary/course-summary.page.ts @@ -39,7 +39,6 @@ import { CoreColors } from '@singletons/colors'; import { CorePath } from '@singletons/path'; import { CorePromisedValue } from '@classes/promised-value'; import { CorePlatform } from '@services/platform'; -import { CoreCourse } from '@features/course/services/course'; import { CorePasswordModalResponse } from '@components/password-modal/password-modal'; import { CoreTime } from '@singletons/time'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; @@ -316,32 +315,40 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy { const guestInstanceId = await this.guestInstanceId; if (this.useGuestAccess && this.guestAccessPasswordRequired && guestInstanceId) { // Check if the user has access to the course as guest with a previous sent password. - let validated = await CoreUtils.promiseWorks( - CoreCourse.getSections(this.courseId, true, true, { getFromCache: false, emergencyCache: false }, undefined, false), - ); + let validated = await CoreCourseHelper.userHasAccessToCourse(this.courseId); if (!validated) { try { - const validatePassword = async (password: string): Promise => { - const response = await CoreCourses.validateGuestAccessPassword(guestInstanceId, password); - - validated = response.validated; - let error = response.hint; - if (!validated && !error) { - error = 'core.course.guestaccess_passwordinvalid'; + type ValidatorResponse = CorePasswordModalResponse & { cancel?: boolean }; + const validatePassword = async (password: string): Promise => { + try { + const response = await CoreCourses.validateGuestAccessPassword(guestInstanceId, password); + + validated = response.validated; + let error = response.hint; + if (!validated && !error) { + error = 'core.course.guestaccess_passwordinvalid'; + } + + return { + password, validated, error, + }; + } catch { + this.refreshData(); + + return { + password, + cancel: true, + }; } - - return { - password, validated, error, - }; }; - const response = await CoreDomUtils.promptPassword({ + const response = await CoreDomUtils.promptPassword({ title: 'core.course.guestaccess', validator: validatePassword, }); - if (!response.validated) { + if (!response.validated || response.cancel) { return; } } catch { diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index 7c02eb06f34..40a5c87d9cf 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -1940,6 +1940,24 @@ export class CoreCourseHelperProvider { } } + /** + * Check if user can access the course. + * + * @param courseId Course ID. + * @returns Promise resolved with boolean: whether user can access the course. + */ + async userHasAccessToCourse(courseId: number): Promise { + if (CoreNetwork.isOnline()) { + return CoreUtils.promiseWorks( + CoreCourse.getSections(courseId, true, true, { getFromCache: false, emergencyCache: false }, undefined, false), + ); + } else { + return CoreUtils.promiseWorks( + CoreCourse.getSections(courseId, true, true, { getCacheUsingCacheKey: true }, undefined, false), + ); + } + } + /** * Delete course files. * diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index caa533f8910..d17696b342f 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -1887,11 +1887,11 @@ export class CoreDomUtilsProvider { * @param passwordParams Params to show the modal. * @returns Entered password, error and validation. */ - async promptPassword(passwordParams?: CorePasswordModalParams): Promise { + async promptPassword(passwordParams?: CorePasswordModalParams): Promise { const { CorePasswordModalComponent } = await import('@/core/components/password-modal/password-modal.module'); - const modalData = await CoreDomUtils.openModal( + const modalData = await CoreDomUtils.openModal( { cssClass: 'core-password-modal', showBackdrop: true, From 67cea11ca7c4bd640cf274531d97f5fdb85df61e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 17 Jul 2023 12:31:27 +0200 Subject: [PATCH 2/2] MOBILE-4009 guest: Do not enrol on course link and use course summary --- .../course-list-item/course-list-item.ts | 2 +- .../courses/services/handlers/course-link.ts | 216 ++---------------- 2 files changed, 26 insertions(+), 192 deletions(-) diff --git a/src/core/features/courses/components/course-list-item/course-list-item.ts b/src/core/features/courses/components/course-list-item/course-list-item.ts index 121b448b688..7d603ef4935 100644 --- a/src/core/features/courses/components/course-list-item/course-list-item.ts +++ b/src/core/features/courses/components/course-list-item/course-list-item.ts @@ -176,7 +176,7 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On */ openCourse(): void { if (this.isEnrolled) { - CoreCourseHelper.openCourse(this.course); + CoreCourseHelper.openCourse(this.course, { params: { isGuest: false } }); } else { CoreNavigator.navigateToSitePath( `/course/${this.course.id}/summary`, diff --git a/src/core/features/courses/services/handlers/course-link.ts b/src/core/features/courses/services/handlers/course-link.ts index c1c3d8b5900..4c60c16b6fe 100644 --- a/src/core/features/courses/services/handlers/course-link.ts +++ b/src/core/features/courses/services/handlers/course-link.ts @@ -18,15 +18,13 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreCourse } from '@features/course/services/course'; import { CoreCourseHelper } from '@features/course/services/course-helper'; -import { CoreCourseAnyCourseData, CoreCourses, CoreCoursesProvider, CoreEnrolledCourseData } from '../courses'; +import { CoreCourses } from '../courses'; import { CoreLogger } from '@singletons/logger'; -import { makeSingleton, Translate } from '@singletons'; +import { makeSingleton } from '@singletons'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; import { Params } from '@angular/router'; -import { CoreError } from '@classes/errors/error'; import { CoreUtils } from '@services/utils/utils'; -import { CoreTextUtils } from '@services/utils/text'; -import { CoreIonLoadingElement } from '@classes/ion-loading'; +import { CoreNavigator } from '@services/navigator'; /** * Handler to treat links to course view or enrol (except site home). @@ -76,7 +74,7 @@ export class CoreCoursesCourseLinkHandlerService extends CoreContentLinksHandler return [{ action: (siteId): void => { siteId = siteId || CoreSites.getCurrentSiteId(); - if (siteId == CoreSites.getCurrentSiteId()) { + if (siteId === CoreSites.getCurrentSiteId()) { // Check if we already are in the course index page. if (CoreCourse.currentViewIsCourse(courseId)) { // Current view is this course, just select the contents tab. @@ -119,209 +117,45 @@ export class CoreCoursesCourseLinkHandlerService extends CoreContentLinksHandler * @returns Promise resolved when done. */ protected async actionOpen(courseId: number, url: string, pageParams: Params): Promise { - const modal = await CoreDomUtils.showModalLoading(); - let course: CoreCourseAnyCourseData | { id: number } | undefined; - - // Check if user is enrolled in the course. - try { - course = await CoreCourses.getUserCourse(courseId); - } catch { - course = await this.checkSelfUserCanSelfEnrolOrAccess(courseId, url, modal); - } - - // Check if we need to retrieve the course. - if (!course) { - try { - const data = await CoreCourseHelper.getCourse(courseId); - course = data.course; - } catch { - // Cannot get course, return a "fake". - course = { id: courseId }; - } - } - - modal.dismiss(); - - // Now open the course. - CoreCourseHelper.openCourse(course, { params: pageParams }); - } - - /** - * Check if the user can self enrol or access the course. - * - * @param courseId Course ID. - * @param url Treated URL. - * @param modal Modal, to dismiss when needed. - * @returns The course after self enrolling or undefined if the user has access but is not enrolled. - */ - protected async checkSelfUserCanSelfEnrolOrAccess( - courseId: number, - url: string, - modal: CoreIonLoadingElement, - ): Promise { const isEnrolUrl = !!url.match(/(\/enrol\/index\.php)|(\/course\/enrol\.php)/); + if (isEnrolUrl) { + this.navigateCourseSummary(courseId, pageParams); - if (!isEnrolUrl) { - // Not an enrol URL, check if the user can access the course (e.g. guest access). - const canAccess = await this.canAccess(courseId); - if (canAccess) { - return; - } + return; } - // User cannot access the course or it's an enrol URL. Check if can self enrol. - const canSelfEnrol = await this.canSelfEnrol(courseId); + const modal = await CoreDomUtils.showModalLoading(); - if (!canSelfEnrol) { - if (isEnrolUrl) { - // Cannot self enrol, check if the user can access the course (e.g. guest access). - const canAccess = await this.canAccess(courseId); - if (canAccess) { - return; - } - } + // Check if user is enrolled in the course. + const hasAccess = await CoreCourseHelper.userHasAccessToCourse(courseId); - // Cannot self enrol and cannot access. Show error and allow the user to open the link in browser. - modal.dismiss(); - const notEnrolledMessage = Translate.instant('core.courses.notenroled'); - const body = CoreTextUtils.buildSeveralParagraphsMessage( - [notEnrolledMessage, Translate.instant('core.confirmopeninbrowser')], - ); + const guestInfo = await CoreCourseHelper.courseUsesGuestAccessInfo(courseId); + pageParams.isGuest = guestInfo.guestAccess; - try { - await CoreDomUtils.showConfirm(body); + if (hasAccess && !guestInfo.guestAccess && !guestInfo.passwordRequired) { + // Direct access. + const course = await CoreUtils.ignoreErrors(CoreCourses.getUserCourse(courseId), { id: courseId }); - CoreSites.getCurrentSite()?.openInBrowserWithAutoLogin(url, undefined, { showBrowserWarning: false }); - } catch { - // User cancelled. - } + CoreCourseHelper.openCourse(course, pageParams); + } else { + this.navigateCourseSummary(courseId, pageParams); - throw new CoreError(notEnrolledMessage); } - // The user can self enrol. If it's not a enrolment URL we'll ask for confirmation. modal.dismiss(); - - if (!isEnrolUrl) { - await CoreDomUtils.showConfirm(Translate.instant('core.courses.confirmselfenrol')); - } - - try { - return await this.selfEnrol(courseId); - } catch (error) { - if (error) { - CoreDomUtils.showErrorModal(error); - } - - throw error; - } - } - - /** - * Check if user can access the course. - * - * @param courseId Course ID. - * @returns Promise resolved with boolean: whether user can access the course. - */ - protected canAccess(courseId: number): Promise { - return CoreUtils.promiseWorks(CoreCourse.getSections(courseId, false, true)); } /** - * Check if a user can be "automatically" self enrolled in a course. + * Navigate course summary. * * @param courseId Course ID. - * @returns Promise resolved with boolean: whether the user can be enrolled in a course. - */ - protected async canSelfEnrol(courseId: number): Promise { - try { - // Check that the course has self enrolment enabled. - const methods = await CoreCourses.getCourseEnrolmentMethods(courseId); - - let isSelfEnrolEnabled = false; - let instances = 0; - methods.forEach((method) => { - if (method.type == 'self' && CoreUtils.isTrueOrOne(method.status)) { - isSelfEnrolEnabled = true; - instances++; - } - }); - - return isSelfEnrolEnabled && instances === 1; - } catch { - return false; - } - } - - /** - * Try to self enrol a user in a course. - * - * @param courseId Course ID. - * @param password Password. - * @returns Promise resolved when the user is enrolled, rejected otherwise. - */ - protected async selfEnrol(courseId: number, password?: string): Promise { - const modal = await CoreDomUtils.showModalLoading(); - - try { - await CoreCourses.selfEnrol(courseId, password); - - try { - // Sometimes the list of enrolled courses takes a while to be updated. Wait for it. - return await this.waitForEnrolled(courseId, true); - } finally { - modal.dismiss(); - } - } catch (error) { - modal.dismiss(); - - if (error && error.errorcode === CoreCoursesProvider.ENROL_INVALID_KEY) { - // Invalid password. Allow the user to input password. - const title = Translate.instant('core.courses.selfenrolment'); - let body = ' '; // Empty message. - const placeholder = Translate.instant('core.courses.password'); - - if (password !== undefined) { - // The user attempted a password. Show an error message. - body = CoreTextUtils.getErrorMessageFromError(error) || body; - } - - password = await CoreDomUtils.showPrompt(body, title, placeholder); - - await this.selfEnrol(courseId, password); - } else { - throw error; - } - } - } - - /** - * Wait for the user to be enrolled in a course. - * - * @param courseId The course ID. - * @param first If it's the first call (true) or it's a recursive call (false). - * @returns Promise resolved when enrolled or timeout. + * @param pageParams Params to send to the new page. */ - protected async waitForEnrolled(courseId: number, first?: boolean): Promise { - if (first) { - this.waitStart = Date.now(); - } - - // Check if user is enrolled in the course. - await CoreUtils.ignoreErrors(CoreCourses.invalidateUserCourses()); - try { - return await CoreCourses.getUserCourse(courseId); - } catch { - // Not enrolled, wait a bit and try again. - if (Date.now() - this.waitStart > 60000) { - // Max time reached, stop. - return; - } - - await CoreUtils.wait(5000); - - return this.waitForEnrolled(courseId); - } + protected navigateCourseSummary(courseId: number, pageParams: Params): void { + CoreNavigator.navigateToSitePath( + `/course/${courseId}/summary`, + { params: pageParams }, + ); } }