From 1d39a3ed2816d8d6556c1a1cf08c836db7833ce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 3 Feb 2021 14:47:02 +0100 Subject: [PATCH] MOBILE-3660 courses: Implement missing handlers --- .../services/handlers/push-click.ts | 8 +- src/core/features/course/services/course.ts | 2 +- src/core/features/courses/courses.module.ts | 12 + .../courses/pages/dashboard/dashboard.ts | 1 + src/core/features/courses/services/courses.ts | 2 - .../courses/services/handlers/course-link.ts | 342 ++++++++++++++++++ .../services/handlers/courses-index-link.ts | 65 ++++ .../services/handlers/dashboard-home.ts | 31 +- .../services/handlers/dashboard-link.ts | 57 +++ .../services/handlers/enrol-push-click.ts | 91 +++++ .../services/handlers/my-courses-home.ts | 7 + .../services/handlers/request-push-click.ts | 103 ++++++ .../mainmenu/services/handlers/mainmenu.ts | 12 +- 13 files changed, 717 insertions(+), 16 deletions(-) create mode 100644 src/core/features/courses/services/handlers/course-link.ts create mode 100644 src/core/features/courses/services/handlers/courses-index-link.ts create mode 100644 src/core/features/courses/services/handlers/dashboard-link.ts create mode 100644 src/core/features/courses/services/handlers/enrol-push-click.ts create mode 100644 src/core/features/courses/services/handlers/request-push-click.ts diff --git a/src/addons/notifications/services/handlers/push-click.ts b/src/addons/notifications/services/handlers/push-click.ts index 8b8e00723..32985b0b2 100644 --- a/src/addons/notifications/services/handlers/push-click.ts +++ b/src/addons/notifications/services/handlers/push-click.ts @@ -41,7 +41,7 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi * @param notification The notification to check. * @return Whether the notification click is handled by this handler */ - async handles(notification: NotificationData): Promise { + async handles(notification: AddonNotificationsNotificationData): Promise { if (!notification.moodlecomponent) { // The notification doesn't come from Moodle. Handle it. return true; @@ -63,7 +63,7 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi * @param notification Notification to mark. * @return Promise resolved when done. */ - protected async markAsRead(notification: NotificationData): Promise { + protected async markAsRead(notification: AddonNotificationsNotificationData): Promise { const notifId = notification.savedmessageid || notification.id; if (!notifId) { @@ -81,7 +81,7 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi * @param notification The notification to check. * @return Promise resolved when done. */ - async handleClick(notification: NotificationData): Promise { + async handleClick(notification: AddonNotificationsNotificationData): Promise { if (notification.customdata?.extendedtext) { // Display the text in a modal. @@ -133,7 +133,7 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi export class AddonNotificationsPushClickHandler extends makeSingleton(AddonNotificationsPushClickHandlerService) {} -type NotificationData = CorePushNotificationsNotificationBasicData & { +type AddonNotificationsNotificationData = CorePushNotificationsNotificationBasicData & { contexturl?: string; // URL related to the notification. savedmessageid?: number; // Notification ID (optional). id?: number; // Notification ID (optional). diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index b0f743abe..25f2fab6f 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -162,7 +162,7 @@ export class CoreCourseProvider { * @return Whether the current view is a certain course. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - currentViewIsCourse(navCtrl: any, courseId: number): boolean { + currentViewIsCourse(courseId: number): boolean { // @todo implement return false; } diff --git a/src/core/features/courses/courses.module.ts b/src/core/features/courses/courses.module.ts index e47bfb9f7..52a5e34c8 100644 --- a/src/core/features/courses/courses.module.ts +++ b/src/core/features/courses/courses.module.ts @@ -14,12 +14,19 @@ import { APP_INITIALIZER, NgModule } from '@angular/core'; import { Routes } from '@angular/router'; +import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreMainMenuHomeRoutingModule } from '@features/mainmenu/pages/home/home-routing.module'; import { CoreMainMenuHomeDelegate } from '@features/mainmenu/services/home-delegate'; +import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate'; +import { CoreCoursesCourseLinkHandler } from './services/handlers/course-link'; +import { CoreCoursesIndexLinkHandler } from './services/handlers/courses-index-link'; import { CoreDashboardHomeHandler, CoreDashboardHomeHandlerService } from './services/handlers/dashboard-home'; +import { CoreCoursesDashboardLinkHandler } from './services/handlers/dashboard-link'; +import { CoreCoursesEnrolPushClickHandler } from './services/handlers/enrol-push-click'; import { CoreCoursesMyCoursesHomeHandler, CoreCoursesMyCoursesHomeHandlerService } from './services/handlers/my-courses-home'; +import { CoreCoursesRequestPushClickHandler } from './services/handlers/request-push-click'; const mainMenuHomeChildrenRoutes: Routes = [ { @@ -59,6 +66,11 @@ const mainMenuHomeSiblingRoutes: Routes = [ useFactory: () => () => { CoreMainMenuHomeDelegate.instance.registerHandler(CoreDashboardHomeHandler.instance); CoreMainMenuHomeDelegate.instance.registerHandler(CoreCoursesMyCoursesHomeHandler.instance); + CoreContentLinksDelegate.instance.registerHandler(CoreCoursesCourseLinkHandler.instance); + CoreContentLinksDelegate.instance.registerHandler(CoreCoursesIndexLinkHandler.instance); + CoreContentLinksDelegate.instance.registerHandler(CoreCoursesDashboardLinkHandler.instance); + CorePushNotificationsDelegate.instance.registerClickHandler(CoreCoursesEnrolPushClickHandler.instance); + CorePushNotificationsDelegate.instance.registerClickHandler(CoreCoursesRequestPushClickHandler.instance); }, }, ], diff --git a/src/core/features/courses/pages/dashboard/dashboard.ts b/src/core/features/courses/pages/dashboard/dashboard.ts index f32c3b666..26514f819 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.ts @@ -161,6 +161,7 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { * Open page to manage courses storage. */ manageCoursesStorage(): void { + // AddonStorageManagerCoursesStoragePage // @todo this.navCtrl.navigateForward(['/main/home/courses/storage']); } diff --git a/src/core/features/courses/services/courses.ts b/src/core/features/courses/services/courses.ts index 54e64f496..84ce04a4f 100644 --- a/src/core/features/courses/services/courses.ts +++ b/src/core/features/courses/services/courses.ts @@ -1168,8 +1168,6 @@ export class CoreCourses extends makeSingleton(CoreCoursesProvider) {} /** * Data sent to the EVENT_MY_COURSES_UPDATED. - * - * @todo course type. */ export type CoreCoursesMyCoursesUpdatedEventData = { action: string; // Action performed. diff --git a/src/core/features/courses/services/handlers/course-link.ts b/src/core/features/courses/services/handlers/course-link.ts new file mode 100644 index 000000000..bd58a16bf --- /dev/null +++ b/src/core/features/courses/services/handlers/course-link.ts @@ -0,0 +1,342 @@ +// (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 { CoreSites } from '@services/sites'; +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 { CoreLogger } from '@singletons/logger'; +import { makeSingleton, Translate } 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'; + +/** + * Handler to treat links to course view or enrol (except site home). + */ +@Injectable({ providedIn: 'root' }) +export class CoreCoursesCourseLinkHandlerService extends CoreContentLinksHandlerBase { + + name = 'CoreCoursesCourseLinkHandler'; + pattern = /((\/enrol\/index\.php)|(\/course\/enrol\.php)|(\/course\/view\.php)).*([?&]id=\d+)/; + + protected waitStart = 0; + protected logger: CoreLogger; + + constructor() { + super(); + + this.logger = CoreLogger.getInstance('CoreCoursesCourseLinkHandler'); + } + + /** + * Get the list of actions for a link (url). + * + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. + */ + getActions( + siteIds: string[], + url: string, + params: Params, + ): CoreContentLinksAction[] | Promise { + const courseId = parseInt(params.id, 10); + const sectionId = params.sectionid ? parseInt(params.sectionid, 10) : null; + const pageParams: Params = { + sectionId: sectionId || null, + }; + let sectionNumber = typeof params.section != 'undefined' ? parseInt(params.section, 10) : NaN; + + if (!sectionId && !sectionNumber) { + // Check if the URL has a hash to navigate to the section. + const matches = url.match(/#section-(\d+)/); + if (matches && matches[1]) { + sectionNumber = parseInt(matches[1], 10); + } + } + + if (!isNaN(sectionNumber)) { + pageParams.sectionNumber = sectionNumber; + } + + return [{ + action: (siteId): void => { + siteId = siteId || CoreSites.instance.getCurrentSiteId(); + if (siteId == CoreSites.instance.getCurrentSiteId()) { + // Check if we already are in the course index page. + if (CoreCourse.instance.currentViewIsCourse(courseId)) { + // Current view is this course, just select the contents tab. + CoreCourse.instance.selectCourseTab('', pageParams); + + return; + } else { + this.actionEnrol(courseId, url, pageParams).catch(() => { + // Ignore errors. + }); + } + } else { + // Make the course the new history root (to avoid "loops" in history). + CoreCourseHelper.instance.getAndOpenCourse(courseId, pageParams, siteId); + } + }, + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. + */ + async isEnabled(siteId: string, url: string, params: Params, courseId?: number): Promise { + courseId = parseInt(params.id, 10); + + if (!courseId) { + return false; + } + + // Get the course id of Site Home. + return CoreSites.instance.getSiteHomeId(siteId).then((siteHomeId) => courseId != siteHomeId); + } + + /** + * Action to perform when an enrol link is clicked. + * + * @param courseId Course ID. + * @param url Treated URL. + * @param pageParams Params to send to the new page. + * @return Promise resolved when done. + */ + protected async actionEnrol(courseId: number, url: string, pageParams: Params): Promise { + const modal = await CoreDomUtils.instance.showModalLoading(); + let course: CoreCourseAnyCourseData | { id: number } | undefined; + + // Check if user is enrolled in the course. + try { + course = await CoreCourses.instance.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.instance.getCourse(courseId); + course = data.course; + } catch { + // Cannot get course, return a "fake". + course = { id: courseId }; + } + } + + modal.dismiss(); + + // Now open the course. + CoreCourseHelper.instance.openCourse(course, 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. + * @return The course after self enrolling or undefined if the user has access but is not enrolled. + */ + protected checkSelfUserCanSelfEnrolOrAccess( + courseId: number, + url: string, + modal: CoreIonLoadingElement, + ): Promise { + // User is not enrolled in the course. Check if can self enrol. + return this.canSelfEnrol(courseId).then(async () => { + modal.dismiss(); + + const isEnrolUrl = !!url.match(/(\/enrol\/index\.php)|(\/course\/enrol\.php)/); + + // The user can self enrol. If it's not a enrolment URL we'll ask for confirmation. + if (!isEnrolUrl) { + try { + await CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.courses.confirmselfenrol')); + } catch { + // User cancelled. Check if the user can view the course contents (guest access or similar). + await CoreCourse.instance.getSections(courseId, false, true); + + return; + } + } + + // Enrol URL or user confirmed. + try { + return this.selfEnrol(courseId); + } catch (error) { + if (error) { + CoreDomUtils.instance.showErrorModal(error); + } + + throw error; + } + }, async (error) => { + // Can't self enrol. Check if the user can view the course contents (guest access or similar). + try { + await CoreCourse.instance.getSections(courseId, false, true); + } catch { + // Error. Show error message and allow the user to open the link in browser. + modal.dismiss(); + + if (error) { + error = CoreTextUtils.instance.getErrorMessageFromError(error) || error; + } + if (!error) { + error = Translate.instance.instant('core.courses.notenroled'); + } + + const body = CoreTextUtils.instance.buildSeveralParagraphsMessage( + [error, Translate.instance.instant('core.confirmopeninbrowser')], + ); + + try { + await CoreDomUtils.instance.showConfirm(body); + + CoreSites.instance.getCurrentSite()?.openInBrowserWithAutoLogin(url); + } catch { + // User cancelled. + }; + + throw error; + } + + return undefined; + }); + } + + /** + * Check if a user can be "automatically" self enrolled in a course. + * + * @param courseId Course ID. + * @return Promise resolved if user can be enrolled in a course, rejected otherwise. + */ + protected async canSelfEnrol(courseId: number): Promise { + // Check that the course has self enrolment enabled. + + const methods = await CoreCourses.instance.getCourseEnrolmentMethods(courseId); + let isSelfEnrolEnabled = false; + let instances = 0; + methods.forEach((method) => { + if (method.type == 'self' && method.status) { + isSelfEnrolEnabled = true; + instances++; + } + }); + + if (!isSelfEnrolEnabled || instances != 1) { + // Self enrol not enabled or more than one instance. + throw new CoreError('Self enrol not enabled in course'); + } + } + + /** + * Try to self enrol a user in a course. + * + * @param courseId Course ID. + * @param password Password. + * @return Promise resolved when the user is enrolled, rejected otherwise. + */ + protected async selfEnrol(courseId: number, password?: string): Promise { + const modal = await CoreDomUtils.instance.showModalLoading(); + + try { + await CoreCourses.instance.selfEnrol(courseId, password); + + // Success self enrolling the user, invalidate the courses list. + await CoreUtils.instance.ignoreErrors(CoreCourses.instance.invalidateUserCourses()); + + try { + // Sometimes the list of enrolled courses takes a while to be updated. Wait for it. + return this.waitForEnrolled(courseId, true); + } finally { + modal.dismiss(); + } + } catch (error) { + modal.dismiss(); + + if (error && error.code === CoreCoursesProvider.ENROL_INVALID_KEY) { + // Invalid password. Allow the user to input password. + const title = Translate.instance.instant('core.courses.selfenrolment'); + const body = ' '; // Empty message. + const placeholder = Translate.instance.instant('core.courses.password'); + + if (typeof password != 'undefined') { + // The user attempted a password. Show an error message. + CoreDomUtils.instance.showErrorModal(error); + } + + password = await CoreDomUtils.instance.showPrompt(body, title, placeholder); + + return 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). + * @return Promise resolved when enrolled or timeout. + */ + protected async waitForEnrolled(courseId: number, first?: boolean): Promise { + if (first) { + this.waitStart = Date.now(); + } + + // Check if user is enrolled in the course. + await CoreUtils.instance.ignoreErrors(CoreCourses.instance.invalidateUserCourses()); + try { + return CoreCourses.instance.getUserCourse(courseId); + } catch { + + // Not enrolled, wait a bit and try again. + if (Date.now() - this.waitStart > 60000) { + // Max time reached, stop. + return; + } + + return new Promise((resolve, reject): void => { + setTimeout(() => { + this.waitForEnrolled(courseId) + .then(resolve).catch(reject); + }, 5000); + }); + } + } + +} + +export class CoreCoursesCourseLinkHandler extends makeSingleton(CoreCoursesCourseLinkHandlerService) {} diff --git a/src/core/features/courses/services/handlers/courses-index-link.ts b/src/core/features/courses/services/handlers/courses-index-link.ts new file mode 100644 index 000000000..52179a846 --- /dev/null +++ b/src/core/features/courses/services/handlers/courses-index-link.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 { Injectable } from '@angular/core'; +import { Params } from '@angular/router'; +import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; +import { CoreNavigator } from '@services/navigator'; +import { makeSingleton } from '@singletons'; +import { CoreCourses } from '../courses'; +import { CoreCoursesMyCoursesHomeHandlerService } from './my-courses-home'; + +/** + * Handler to treat links to course index (list of courses). + */ +@Injectable({ providedIn: 'root' }) +export class CoreCoursesIndexLinkHandlerService extends CoreContentLinksHandlerBase { + + name = 'CoreCoursesIndexLinkHandler'; + featureName = 'CoreMainMenuDelegate_CoreCourses'; + pattern = /\/course\/?(index\.php.*)?$/; + + /** + * Get the list of actions for a link (url). + * + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @return List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] { + return [{ + action: (siteId): void => { + let pageName = CoreCoursesMyCoursesHomeHandlerService.PAGE_NAME; + + if (CoreCourses.instance.isGetCoursesByFieldAvailable()) { + if (params.categoryid) { + pageName += '/categories/' + params.categoryid; + } else { + pageName += '/all'; + } + } else { + // By default, go to My Courses. + pageName += '/my'; + } + + CoreNavigator.instance.navigateToSitePath(pageName, { siteId }); + }, + }]; + } + +} + +export class CoreCoursesIndexLinkHandler extends makeSingleton(CoreCoursesIndexLinkHandlerService) {} diff --git a/src/core/features/courses/services/handlers/dashboard-home.ts b/src/core/features/courses/services/handlers/dashboard-home.ts index e3a3edb1c..b4f4e8ce0 100644 --- a/src/core/features/courses/services/handlers/dashboard-home.ts +++ b/src/core/features/courses/services/handlers/dashboard-home.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { AddonBlockTimeline } from '@addons/block/timeline/services/timeline'; import { Injectable } from '@angular/core'; import { CoreBlockDelegate } from '@features/block/services/block-delegate'; import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerToDisplay } from '@features/mainmenu/services/home-delegate'; @@ -45,9 +46,35 @@ export class CoreDashboardHomeHandlerService implements CoreMainMenuHomeHandler * @return Whether or not the handler is enabled on a site level. */ async isEnabledForSite(siteId?: string): Promise { - const blocks = await CoreCoursesDashboard.instance.getDashboardBlocks(undefined, siteId); + const promises: Promise[] = []; + let blocksEnabled = false; + let dashboardAvailable = false; - return CoreBlockDelegate.instance.hasSupportedBlock(blocks); + // Check if blocks and 3.6 dashboard is enabled. + promises.push(CoreBlockDelegate.instance.areBlocksDisabled(siteId).then((disabled) => { + blocksEnabled = !disabled; + + return; + })); + + promises.push(CoreCoursesDashboard.instance.isAvailable().then((available) => { + dashboardAvailable = available; + + return; + })); + + await Promise.all(promises); + + if (dashboardAvailable && blocksEnabled) { + const blocks = await CoreCoursesDashboard.instance.getDashboardBlocks(undefined, siteId); + + return CoreBlockDelegate.instance.hasSupportedBlock(blocks); + } + + // Check if my overview is enabled. If it's enabled we will fake enabled blocks. + const timelineEnabled = await AddonBlockTimeline.instance.isAvailable(); + + return timelineEnabled && blocksEnabled; } /** diff --git a/src/core/features/courses/services/handlers/dashboard-link.ts b/src/core/features/courses/services/handlers/dashboard-link.ts new file mode 100644 index 000000000..31c643165 --- /dev/null +++ b/src/core/features/courses/services/handlers/dashboard-link.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 { Injectable } from '@angular/core'; +import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; +import { CoreNavigator } from '@services/navigator'; +import { makeSingleton } from '@singletons'; +import { CoreDashboardHomeHandler, CoreDashboardHomeHandlerService } from './dashboard-home'; + +/** + * Handler to treat links to my overview. + */ +@Injectable({ providedIn: 'root' }) +export class CoreCoursesDashboardLinkHandlerService extends CoreContentLinksHandlerBase { + + name = 'CoreCoursesMyOverviewLinkHandler'; + pattern = /\/my\/?$/; + + /** + * Get the list of actions for a link (url). + * + * @return List of (or promise resolved with list of) actions. + */ + getActions(): CoreContentLinksAction[] | Promise { + return [{ + action: (siteId): void => { + // Use redirect to select the tab. + CoreNavigator.instance.navigateToSitePath(CoreDashboardHomeHandlerService.PAGE_NAME, { siteId }); + }, + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * + * @param siteId The site ID. + * @return Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string): boolean | Promise { + return CoreDashboardHomeHandler.instance.isEnabledForSite(siteId); + } + +} + +export class CoreCoursesDashboardLinkHandler extends makeSingleton(CoreCoursesDashboardLinkHandlerService) {} diff --git a/src/core/features/courses/services/handlers/enrol-push-click.ts b/src/core/features/courses/services/handlers/enrol-push-click.ts new file mode 100644 index 000000000..e141ad109 --- /dev/null +++ b/src/core/features/courses/services/handlers/enrol-push-click.ts @@ -0,0 +1,91 @@ +// (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 { Params } from '@angular/router'; +import { CoreCourseHelper } from '@features/course/services/course-helper'; +import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate'; +import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications'; +import { CoreNavigator } from '@services/navigator'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; + +/** + * Handler for enrol push notifications clicks. + */ +@Injectable({ providedIn: 'root' }) +export class CoreCoursesEnrolPushClickHandlerService implements CorePushNotificationsClickHandler { + + name = 'CoreCoursesEnrolPushClickHandler'; + priority = 200; + + /** + * Check if a notification click is handled by this handler. + * + * @param notification The notification to check. + * @return Whether the notification click is handled by this handler + */ + async handles(notification: CorePushNotificationsNotificationBasicData): Promise { + return CoreUtils.instance.isTrueOrOne(notification.notif) && notification.moodlecomponent?.indexOf('enrol_') === 0 && + notification.name == 'expiry_notification'; + } + + /** + * Handle the notification click. + * + * @param notification The notification to check. + * @return Promise resolved when done. + */ + async handleClick(notification: CoreCoursesEnrolNotificationData): Promise { + const courseId = notification.courseid; + + const modal = await CoreDomUtils.instance.showModalLoading(); + + try { + const result = await CoreCourseHelper.instance.getCourse(courseId, notification.site); + + const params: Params = { + course: result.course, + }; + let page: string; + + if (notification.contexturl?.indexOf('user/index.php') != -1) { + // Open the participants tab. + page = 'course'; + params.selectedTab = 'user_participants'; // @todo: Set this when participants is done. + } else if (result.enrolled) { + // User is still enrolled, open the course. + page = 'course'; + } else { + // User not enrolled anymore, open the preview page. + page = 'courses/preview';; + } + + await CoreNavigator.instance.navigateToSitePath(page, { params, siteId: notification.site }); + } catch (error) { + CoreDomUtils.instance.showErrorModalDefault(error, 'Error getting course.'); + } finally { + modal.dismiss(); + } + } + +} + +export class CoreCoursesEnrolPushClickHandler extends makeSingleton(CoreCoursesEnrolPushClickHandlerService) {} + +type CoreCoursesEnrolNotificationData = CorePushNotificationsNotificationBasicData & { + courseid: number; // Course ID related to the notification. + contexturl?: string; // Context URL related to the notification. +}; diff --git a/src/core/features/courses/services/handlers/my-courses-home.ts b/src/core/features/courses/services/handlers/my-courses-home.ts index 43796c803..4cfd9a9a4 100644 --- a/src/core/features/courses/services/handlers/my-courses-home.ts +++ b/src/core/features/courses/services/handlers/my-courses-home.ts @@ -17,6 +17,7 @@ import { CoreBlockDelegate } from '@features/block/services/block-delegate'; import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerToDisplay } from '@features/mainmenu/services/home-delegate'; import { CoreSiteHome } from '@features/sitehome/services/sitehome'; import { makeSingleton } from '@singletons'; +import { CoreCourses } from '../courses'; import { CoreCoursesDashboard } from '../dashboard'; /** @@ -46,6 +47,12 @@ export class CoreCoursesMyCoursesHomeHandlerService implements CoreMainMenuHomeH * @return Whether or not the handler is enabled on a site level. */ async isEnabledForSite(siteId?: string): Promise { + const disabled = await CoreCourses.instance.isMyCoursesDisabled(siteId); + + if (disabled) { + return false; + } + const blocks = await CoreCoursesDashboard.instance.getDashboardBlocks(undefined, siteId); return !CoreBlockDelegate.instance.hasSupportedBlock(blocks)&& !CoreSiteHome.instance.isAvailable(siteId); diff --git a/src/core/features/courses/services/handlers/request-push-click.ts b/src/core/features/courses/services/handlers/request-push-click.ts new file mode 100644 index 000000000..237573dbd --- /dev/null +++ b/src/core/features/courses/services/handlers/request-push-click.ts @@ -0,0 +1,103 @@ +// (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 { Params } from '@angular/router'; +import { CoreCourseHelper } from '@features/course/services/course-helper'; +import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate'; +import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications'; +import { CoreNavigator } from '@services/navigator'; +import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreTextUtils } from '@services/utils/text'; +import { CoreUtils } from '@services/utils/utils'; +import { makeSingleton } from '@singletons'; +import { CoreCourses } from '../courses'; + + +/** + * Handler for course request push notifications clicks. + */ +@Injectable({ providedIn: 'root' }) +export class CoreCoursesRequestPushClickHandlerService implements CorePushNotificationsClickHandler { + + name = 'CoreCoursesRequestPushClickHandler'; + priority = 200; + + /** + * Check if a notification click is handled by this handler. + * + * @param notification The notification to check. + * @return Whether the notification click is handled by this handler + */ + async handles(notification: CorePushNotificationsNotificationBasicData): Promise { + // Don't support 'courserequestrejected', that way the app will open the notifications page. + return CoreUtils.instance.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'moodle' && + (notification.name == 'courserequested' || notification.name == 'courserequestapproved'); + } + + /** + * Handle the notification click. + * + * @param notification The notification to check. + * @return Promise resolved when done. + */ + async handleClick(notification: CoreCoursesRequestNotificationData): Promise { + const courseId = notification.courseid; + + if (notification.name == 'courserequested') { + // Feature not supported in the app, open in browser. + const site = await CoreSites.instance.getSite(notification.site); + const url = CoreTextUtils.instance.concatenatePaths(site.getURL(), 'course/pending.php'); + + await site.openInBrowserWithAutoLogin(url); + + return; + } + + // Open the course. + const modal = await CoreDomUtils.instance.showModalLoading(); + + await CoreUtils.instance.ignoreErrors(CoreCourses.instance.invalidateUserCourses(notification.site)); + + try { + const result = await CoreCourseHelper.instance.getCourse(courseId, notification.site); + const params: Params = { + course: result.course, + }; + let page: string; + + if (result.enrolled) { + // User is still enrolled, open the course. + page = 'course'; + } else { + // User not enrolled (shouldn't happen), open the preview page. + page = 'courses/preview'; + } + + await CoreNavigator.instance.navigateToSitePath(page, { params, siteId: notification.site }); + } catch (error) { + CoreDomUtils.instance.showErrorModalDefault(error, 'Error getting course.'); + } finally { + modal.dismiss(); + } + } + +} + +export class CoreCoursesRequestPushClickHandler extends makeSingleton(CoreCoursesRequestPushClickHandlerService) {} + +type CoreCoursesRequestNotificationData = CorePushNotificationsNotificationBasicData & { + courseid: number; // Course ID related to the notification. +}; diff --git a/src/core/features/mainmenu/services/handlers/mainmenu.ts b/src/core/features/mainmenu/services/handlers/mainmenu.ts index a530dafa0..17226d49c 100644 --- a/src/core/features/mainmenu/services/handlers/mainmenu.ts +++ b/src/core/features/mainmenu/services/handlers/mainmenu.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import { makeSingleton } from '@singletons'; +import { CoreMainMenuHomeDelegate } from '../home-delegate'; import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../mainmenu-delegate'; /** @@ -24,7 +25,7 @@ export class CoreMainMenuHomeHandlerService implements CoreMainMenuHandler { static readonly PAGE_NAME = 'home'; - name = 'CoreMainMenuHome'; + name = 'CoreHome'; priority = 1100; /** @@ -39,13 +40,10 @@ export class CoreMainMenuHomeHandlerService implements CoreMainMenuHandler { /** * Check if the handler is enabled on a certain site. * - * @param siteId Site ID. If not defined, current site. * @return Whether or not the handler is enabled on a site level. */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async isEnabledForSite(siteId?: string): Promise { - // @todo - return true; + async isEnabledForSite(): Promise { + return CoreMainMenuHomeDelegate.instance.getHandlers().length > 0; } /** @@ -55,7 +53,7 @@ export class CoreMainMenuHomeHandlerService implements CoreMainMenuHandler { */ getDisplayData(): CoreMainMenuHandlerData { return { - icon: 'fa-home', + icon: 'fas-home', title: 'core.mainmenu.home', page: CoreMainMenuHomeHandlerService.PAGE_NAME, // @todo: subPage? The page can change due to core-tabs.