From ac96739e398146da3c0a025d239f85fada7d22e8 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 19 Mar 2019 11:58:28 +0100 Subject: [PATCH] MOBILE-2921 quiz: Support quiz push clicks --- src/addon/mod/quiz/providers/helper.ts | 79 ++++++++++++++++++- .../mod/quiz/providers/push-click-handler.ts | 74 +++++++++++++++++ .../mod/quiz/providers/review-link-handler.ts | 54 +------------ src/addon/mod/quiz/quiz.module.ts | 9 ++- 4 files changed, 162 insertions(+), 54 deletions(-) create mode 100644 src/addon/mod/quiz/providers/push-click-handler.ts diff --git a/src/addon/mod/quiz/providers/helper.ts b/src/addon/mod/quiz/providers/helper.ts index af9d466a8..3eb4f5225 100644 --- a/src/addon/mod/quiz/providers/helper.ts +++ b/src/addon/mod/quiz/providers/helper.ts @@ -13,13 +13,16 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { ModalController } from 'ionic-angular'; +import { ModalController, NavController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; +import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { AddonModQuizProvider } from './quiz'; import { AddonModQuizOfflineProvider } from './quiz-offline'; import { AddonModQuizAccessRuleDelegate } from './access-rules-delegate'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; /** * Helper service that provides some features for quiz. @@ -29,7 +32,9 @@ export class AddonModQuizHelperProvider { constructor(private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider, private accessRuleDelegate: AddonModQuizAccessRuleDelegate, private quizProvider: AddonModQuizProvider, - private modalCtrl: ModalController, private quizOfflineProvider: AddonModQuizOfflineProvider) { } + private modalCtrl: ModalController, private quizOfflineProvider: AddonModQuizOfflineProvider, + private courseHelper: CoreCourseHelperProvider, private sitesProvider: CoreSitesProvider, + private linkHelper: CoreContentLinksHelperProvider) { } /** * Validate a preflight data or show a modal to input the preflight data if required. @@ -156,6 +161,76 @@ export class AddonModQuizHelperProvider { return this.domUtils.getContentsOfElement(element, '.grade'); } + /** + * Get a quiz ID by attempt ID. + * + * @param {number} attemptId Attempt ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the quiz ID. + */ + getQuizIdByAttemptId(attemptId: number, siteId?: string): Promise { + // Use getAttemptReview to retrieve the quiz ID. + return this.quizProvider.getAttemptReview(attemptId, undefined, false, siteId).then((reviewData) => { + if (reviewData.attempt && reviewData.attempt.quiz) { + return reviewData.attempt.quiz; + } + + return Promise.reject(null); + }); + } + + /** + * Handle a review link. + * + * @param {NavController} navCtrl Nav controller, can be undefined/null. + * @param {number} attemptId Attempt ID. + * @param {number} [page] Page to load, -1 to all questions in same page. + * @param {number} [courseId] Course ID. + * @param {number} [quizId] Quiz ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + handleReviewLink(navCtrl: NavController, attemptId: number, page?: number, courseId?: number, quizId?: number, + siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const modal = this.domUtils.showModalLoading(); + let promise; + + if (quizId) { + promise = Promise.resolve(quizId); + } else { + // Retrieve the quiz ID using the attempt ID. + promise = this.getQuizIdByAttemptId(attemptId); + } + + return promise.then((id) => { + quizId = id; + + // Get the courseId if we don't have it. + if (courseId) { + return courseId; + } else { + return this.courseHelper.getModuleCourseIdByInstance(quizId, 'quiz', siteId); + } + }).then((courseId) => { + // Go to the review page. + const pageParams = { + quizId: quizId, + attemptId: attemptId, + courseId: courseId, + page: isNaN(page) ? -1 : page + }; + + return this.linkHelper.goInSite(navCtrl, 'AddonModQuizReviewPage', pageParams, siteId); + }).catch((error) => { + + this.domUtils.showErrorModalDefault(error, 'An error occurred while loading the required data.'); + }).finally(() => { + modal.dismiss(); + }); + } + /** * Add some calculated data to the attempt. * diff --git a/src/addon/mod/quiz/providers/push-click-handler.ts b/src/addon/mod/quiz/providers/push-click-handler.ts new file mode 100644 index 000000000..2373bd622 --- /dev/null +++ b/src/addon/mod/quiz/providers/push-click-handler.ts @@ -0,0 +1,74 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { CoreUrlUtilsProvider } from '@providers/utils/url'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CorePushNotificationsClickHandler } from '@core/pushnotifications/providers/delegate'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { AddonModQuizProvider } from './quiz'; +import { AddonModQuizHelperProvider } from './helper'; + +/** + * Handler for quiz push notifications clicks. + */ +@Injectable() +export class AddonModQuizPushClickHandler implements CorePushNotificationsClickHandler { + name = 'AddonModQuizPushClickHandler'; + priority = 200; + featureName = 'CoreCourseModuleDelegate_AddonModQuiz'; + + protected SUPPORTED_NAMES = ['submission', 'confirmation', 'attempt_overdue']; + + constructor(private utils: CoreUtilsProvider, private quizProvider: AddonModQuizProvider, + private urlUtils: CoreUrlUtilsProvider, private courseHelper: CoreCourseHelperProvider, + private quizHelper: AddonModQuizHelperProvider) {} + + /** + * Check if a notification click is handled by this handler. + * + * @param {any} notification The notification to check. + * @return {boolean} Whether the notification click is handled by this handler + */ + handles(notification: any): boolean | Promise { + return this.utils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'mod_quiz' && + this.SUPPORTED_NAMES.indexOf(notification.name) != -1; + } + + /** + * Handle the notification click. + * + * @param {any} notification The notification to check. + * @return {Promise} Promise resolved when done. + */ + handleClick(notification: any): Promise { + const contextUrlParams = this.urlUtils.extractUrlParams(notification.contexturl), + courseId = Number(notification.courseid); + + if (notification.name == 'submission') { + // A student made a submission, go to view the attempt. + return this.quizHelper.handleReviewLink(undefined, Number(contextUrlParams.attempt), Number(contextUrlParams.page), + courseId, undefined, notification.site); + } else { + // Open the activity. + const moduleId = Number(contextUrlParams.id); + + return this.quizProvider.invalidateContent(moduleId, courseId, notification.site).catch(() => { + // Ignore errors. + }).then(() => { + return this.courseHelper.navigateToModule(moduleId, notification.site, courseId); + }); + } + } +} diff --git a/src/addon/mod/quiz/providers/review-link-handler.ts b/src/addon/mod/quiz/providers/review-link-handler.ts index 2aa6dece7..b5d36dfc3 100644 --- a/src/addon/mod/quiz/providers/review-link-handler.ts +++ b/src/addon/mod/quiz/providers/review-link-handler.ts @@ -13,12 +13,10 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; -import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; -import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { AddonModQuizProvider } from './quiz'; +import { AddonModQuizHelperProvider } from './helper'; /** * Handler to treat links to quiz review. @@ -29,8 +27,7 @@ export class AddonModQuizReviewLinkHandler extends CoreContentLinksHandlerBase { featureName = 'CoreCourseModuleDelegate_AddonModQuiz'; pattern = /\/mod\/quiz\/review\.php.*([\&\?]attempt=\d+)/; - constructor(protected domUtils: CoreDomUtilsProvider, protected quizProvider: AddonModQuizProvider, - protected courseHelper: CoreCourseHelperProvider, protected linkHelper: CoreContentLinksHelperProvider) { + constructor(protected quizProvider: AddonModQuizProvider, protected quizHelper: AddonModQuizHelperProvider) { super(); } @@ -50,56 +47,13 @@ export class AddonModQuizReviewLinkHandler extends CoreContentLinksHandlerBase { return [{ action: (siteId, navCtrl?): void => { - // Retrieve the quiz ID using the attempt ID. - const modal = this.domUtils.showModalLoading(), - attemptId = parseInt(params.attempt, 10), + const attemptId = parseInt(params.attempt, 10), page = parseInt(params.page, 10); - let quizId; - this.getQuizIdByAttemptId(attemptId).then((id) => { - quizId = id; - - // Get the courseId if we don't have it. - if (courseId) { - return courseId; - } else { - return this.courseHelper.getModuleCourseIdByInstance(quizId, 'quiz', siteId); - } - }).then((courseId) => { - // Go to the review page. - const pageParams = { - quizId: quizId, - attemptId: attemptId, - courseId: courseId, - page: params.showall ? -1 : (isNaN(page) ? -1 : page) - }; - - this.linkHelper.goInSite(navCtrl, 'AddonModQuizReviewPage', pageParams, siteId); - }).catch((error) => { - - this.domUtils.showErrorModalDefault(error, 'An error occurred while loading the required data.'); - }).finally(() => { - modal.dismiss(); - }); + this.quizHelper.handleReviewLink(navCtrl, attemptId, page, courseId, undefined, siteId); } }]; } - /** - * Get a quiz ID by attempt ID. - * - * @param {number} attemptId Attempt ID. - * @return {Promise} Promise resolved with the quiz ID. - */ - protected getQuizIdByAttemptId(attemptId: number): Promise { - // Use getAttemptReview to retrieve the quiz ID. - return this.quizProvider.getAttemptReview(attemptId).then((reviewData) => { - if (reviewData.attempt && reviewData.attempt.quiz) { - return reviewData.attempt.quiz; - } - - return Promise.reject(null); - }); - } /** * Check if the handler is enabled for a certain site (site + user) and a URL. diff --git a/src/addon/mod/quiz/quiz.module.ts b/src/addon/mod/quiz/quiz.module.ts index 5f10410cb..93cc7ae2f 100644 --- a/src/addon/mod/quiz/quiz.module.ts +++ b/src/addon/mod/quiz/quiz.module.ts @@ -17,6 +17,7 @@ import { CoreCronDelegate } from '@providers/cron'; import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; +import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate'; import { AddonModQuizAccessRuleDelegate } from './providers/access-rules-delegate'; import { AddonModQuizProvider } from './providers/quiz'; import { AddonModQuizOfflineProvider } from './providers/quiz-offline'; @@ -29,6 +30,7 @@ import { AddonModQuizIndexLinkHandler } from './providers/index-link-handler'; import { AddonModQuizGradeLinkHandler } from './providers/grade-link-handler'; import { AddonModQuizReviewLinkHandler } from './providers/review-link-handler'; import { AddonModQuizListLinkHandler } from './providers/list-link-handler'; +import { AddonModQuizPushClickHandler } from './providers/push-click-handler'; import { AddonModQuizComponentsModule } from './components/components.module'; import { CoreUpdateManagerProvider } from '@providers/update-manager'; @@ -79,7 +81,8 @@ export const ADDON_MOD_QUIZ_PROVIDERS: any[] = [ AddonModQuizIndexLinkHandler, AddonModQuizGradeLinkHandler, AddonModQuizReviewLinkHandler, - AddonModQuizListLinkHandler + AddonModQuizListLinkHandler, + AddonModQuizPushClickHandler ] }) export class AddonModQuizModule { @@ -88,7 +91,8 @@ export class AddonModQuizModule { cronDelegate: CoreCronDelegate, syncHandler: AddonModQuizSyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModQuizIndexLinkHandler, gradeHandler: AddonModQuizGradeLinkHandler, reviewHandler: AddonModQuizReviewLinkHandler, updateManager: CoreUpdateManagerProvider, - listLinkHandler: AddonModQuizListLinkHandler) { + listLinkHandler: AddonModQuizListLinkHandler, + pushNotificationsDelegate: CorePushNotificationsDelegate, pushClickHandler: AddonModQuizPushClickHandler) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); @@ -97,6 +101,7 @@ export class AddonModQuizModule { linksDelegate.registerHandler(gradeHandler); linksDelegate.registerHandler(reviewHandler); linksDelegate.registerHandler(listLinkHandler); + pushNotificationsDelegate.registerClickHandler(pushClickHandler); // Allow migrating the tables from the old app to the new schema. updateManager.registerSiteTableMigration({