diff --git a/src/addon/mod/lesson/lesson.module.ts b/src/addon/mod/lesson/lesson.module.ts index 40da3d1f9..27972cf62 100644 --- a/src/addon/mod/lesson/lesson.module.ts +++ b/src/addon/mod/lesson/lesson.module.ts @@ -13,17 +13,51 @@ // limitations under the License. import { NgModule } from '@angular/core'; +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 { AddonModLessonComponentsModule } from './components/components.module'; import { AddonModLessonProvider } from './providers/lesson'; import { AddonModLessonOfflineProvider } from './providers/lesson-offline'; +import { AddonModLessonSyncProvider } from './providers/lesson-sync'; +import { AddonModLessonModuleHandler } from './providers/module-handler'; +import { AddonModLessonPrefetchHandler } from './providers/prefetch-handler'; +import { AddonModLessonSyncCronHandler } from './providers/sync-cron-handler'; +import { AddonModLessonIndexLinkHandler } from './providers/index-link-handler'; +import { AddonModLessonGradeLinkHandler } from './providers/grade-link-handler'; +import { AddonModLessonReportLinkHandler } from './providers/report-link-handler'; @NgModule({ declarations: [ ], imports: [ + AddonModLessonComponentsModule ], providers: [ AddonModLessonProvider, - AddonModLessonOfflineProvider + AddonModLessonOfflineProvider, + AddonModLessonSyncProvider, + AddonModLessonModuleHandler, + AddonModLessonPrefetchHandler, + AddonModLessonSyncCronHandler, + AddonModLessonIndexLinkHandler, + AddonModLessonGradeLinkHandler, + AddonModLessonReportLinkHandler ] }) -export class AddonModLessonModule { } +export class AddonModLessonModule { + constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModLessonModuleHandler, + prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModLessonPrefetchHandler, + cronDelegate: CoreCronDelegate, syncHandler: AddonModLessonSyncCronHandler, linksDelegate: CoreContentLinksDelegate, + indexHandler: AddonModLessonIndexLinkHandler, gradeHandler: AddonModLessonGradeLinkHandler, + reportHandler: AddonModLessonReportLinkHandler) { + + moduleDelegate.registerHandler(moduleHandler); + prefetchDelegate.registerHandler(prefetchHandler); + cronDelegate.register(syncHandler); + linksDelegate.registerHandler(indexHandler); + linksDelegate.registerHandler(gradeHandler); + linksDelegate.registerHandler(reportHandler); + } +} diff --git a/src/addon/mod/lesson/providers/grade-link-handler.ts b/src/addon/mod/lesson/providers/grade-link-handler.ts new file mode 100644 index 000000000..f71c50586 --- /dev/null +++ b/src/addon/mod/lesson/providers/grade-link-handler.ts @@ -0,0 +1,95 @@ +// (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 { NavController } from 'ionic-angular'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreContentLinksModuleGradeHandler } from '@core/contentlinks/classes/module-grade-handler'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { AddonModLessonProvider } from './lesson'; + +/** + * Handler to treat links to lesson grade. + */ +@Injectable() +export class AddonModLessonGradeLinkHandler extends CoreContentLinksModuleGradeHandler { + name = 'AddonModLessonGradeLinkHandler'; + canReview = true; + + constructor(courseHelper: CoreCourseHelperProvider, domUtils: CoreDomUtilsProvider, sitesProvider: CoreSitesProvider, + protected lessonProvider: AddonModLessonProvider, protected courseProvider: CoreCourseProvider, + protected linkHelper: CoreContentLinksHelperProvider) { + super(courseHelper, domUtils, sitesProvider, 'AddonModLesson', 'lesson'); + } + + /** + * Go to the page to review. + * + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} courseId Course ID related to the URL. + * @param {string} siteId Site to use. + * @param {NavController} [navCtrl] Nav Controller to use to navigate. + * @return {Promise} Promise resolved when done. + */ + protected goToReview(url: string, params: any, courseId: number, siteId: string, navCtrl?: NavController): Promise { + + const moduleId = parseInt(params.id, 10), + modal = this.domUtils.showModalLoading(); + let module; + + return this.courseProvider.getModuleBasicInfo(moduleId, siteId).then((mod) => { + module = mod; + courseId = module.course || courseId || params.courseid || params.cid; + + // Check if the user can see the user reports in the lesson. + return this.lessonProvider.getAccessInformation(module.instance); + }).then((info) => { + if (info.canviewreports) { + // User can view reports, go to view the report. + const pageParams = { + courseId: Number(courseId), + lessonId: module.instance, + userId: parseInt(params.userid, 10) + }; + + this.linkHelper.goInSite(navCtrl, 'AddonModLessonUserRetakePage', pageParams, siteId); + } else { + // User cannot view the report, go to lesson index. + this.courseHelper.navigateToModule(moduleId, siteId, courseId, module.section); + } + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); + }).finally(() => { + modal.dismiss(); + }); + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param {string} siteId The site ID. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + return this.lessonProvider.isPluginEnabled(); + } +} diff --git a/src/addon/mod/lesson/providers/index-link-handler.ts b/src/addon/mod/lesson/providers/index-link-handler.ts new file mode 100644 index 000000000..244c55fcd --- /dev/null +++ b/src/addon/mod/lesson/providers/index-link-handler.ts @@ -0,0 +1,105 @@ +// (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 { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler'; +import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { AddonModLessonProvider } from './lesson'; + +/** + * Handler to treat links to lesson index. + */ +@Injectable() +export class AddonModLessonIndexLinkHandler extends CoreContentLinksModuleIndexHandler { + name = 'AddonModLessonIndexLinkHandler'; + + constructor(courseHelper: CoreCourseHelperProvider, protected lessonProvider: AddonModLessonProvider, + protected domUtils: CoreDomUtilsProvider, protected courseProvider: CoreCourseProvider) { + super(courseHelper, 'AddonModLesson', 'lesson'); + } + + /** + * Get the list of actions for a link (url). + * + * @param {string[]} siteIds List of sites the URL belongs to. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { + + courseId = courseId || params.courseid || params.cid; + + return [{ + action: (siteId, navCtrl?): void => { + /* Ignore the pageid param. If we open the lesson player with a certain page and the user hasn't started + the lesson, an error is thrown: could not find lesson_timer records. */ + if (params.userpassword) { + this.navigateToModuleWithPassword(parseInt(params.id, 10), courseId, params.userpassword, siteId); + } else { + this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId); + } + } + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param {string} siteId The site ID. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + return this.lessonProvider.isPluginEnabled(); + } + + /** + * Navigate to a lesson module (index page) with a fixed password. + * + * @param {number} moduleId Module ID. + * @param {number} courseId Course ID. + * @param {string} password Password. + * @param {string} siteId Site ID. + * @return {Promise} Promise resolved when navigated. + */ + protected navigateToModuleWithPassword(moduleId: number, courseId: number, password: string, siteId: string): Promise { + const modal = this.domUtils.showModalLoading(); + + // Get the module. + return this.courseProvider.getModuleBasicInfo(moduleId, siteId).then((module) => { + courseId = courseId || module.course; + + // Store the password so it's automatically used. + return this.lessonProvider.storePassword(parseInt(module.instance, 10), password, siteId).catch(() => { + // Ignore errors. + }).then(() => { + return this.courseHelper.navigateToModule(moduleId, siteId, courseId, module.section); + }); + }).catch(() => { + // Error, go to index page. + return this.courseHelper.navigateToModule(moduleId, siteId, courseId); + }).finally(() => { + modal.dismiss(); + }); + } +} diff --git a/src/addon/mod/lesson/providers/module-handler.ts b/src/addon/mod/lesson/providers/module-handler.ts new file mode 100644 index 000000000..6ff5f57f2 --- /dev/null +++ b/src/addon/mod/lesson/providers/module-handler.ts @@ -0,0 +1,72 @@ +// (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 { NavController, NavOptions } from 'ionic-angular'; +import { AddonModLessonIndexComponent } from '../components/index/index'; +import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { AddonModLessonProvider } from './lesson'; + +/** + * Handler to support quiz modules. + */ +@Injectable() +export class AddonModLessonModuleHandler implements CoreCourseModuleHandler { + name = 'AddonModLesson'; + modName = 'lesson'; + + constructor(private courseProvider: CoreCourseProvider, private lessonProvider: AddonModLessonProvider) { } + + /** + * Check if the handler is enabled on a site level. + * + * @return {Promise} Promise resolved with boolean: whether or not the handler is enabled on a site level. + */ + isEnabled(): Promise { + return this.lessonProvider.isPluginEnabled(); + } + + /** + * Get the data required to display the module in the course contents view. + * + * @param {any} module The module object. + * @param {number} courseId The course ID. + * @param {number} sectionId The section ID. + * @return {CoreCourseModuleHandlerData} Data to render the module. + */ + getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { + return { + icon: this.courseProvider.getModuleIconSrc('lesson'), + title: module.name, + class: 'addon-mod_lesson-handler', + showDownloadButton: true, + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { + navCtrl.push('AddonModLessonIndexPage', {module: module, courseId: courseId}, options); + } + }; + } + + /** + * Get the component to render the module. This is needed to support singleactivity course format. + * The component returned must implement CoreCourseModuleMainComponent. + * + * @param {any} course The course object. + * @param {any} module The module object. + * @return {any} The component to use, undefined if not found. + */ + getMainComponent(course: any, module: any): any { + return AddonModLessonIndexComponent; + } +} diff --git a/src/addon/mod/lesson/providers/report-link-handler.ts b/src/addon/mod/lesson/providers/report-link-handler.ts new file mode 100644 index 000000000..e07c9978c --- /dev/null +++ b/src/addon/mod/lesson/providers/report-link-handler.ts @@ -0,0 +1,154 @@ +// (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 { NavController } from 'ionic-angular'; +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 { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { AddonModLessonProvider } from './lesson'; + +/** + * Handler to treat links to lesson report. + */ +@Injectable() +export class AddonModLessonReportLinkHandler extends CoreContentLinksHandlerBase { + name = 'AddonModLessonReportLinkHandler'; + featureName = 'CoreCourseModuleDelegate_AddonModLesson'; + pattern = /\/mod\/lesson\/report\.php.*([\&\?]id=\d+)/; + + constructor(protected domUtils: CoreDomUtilsProvider, protected lessonProvider: AddonModLessonProvider, + protected courseHelper: CoreCourseHelperProvider, protected linkHelper: CoreContentLinksHelperProvider, + protected courseProvider: CoreCourseProvider) { + super(); + } + + /** + * Get the list of actions for a link (url). + * + * @param {string[]} siteIds List of sites the URL belongs to. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { + courseId = courseId || params.courseid || params.cid; + + return [{ + action: (siteId, navCtrl?): void => { + if (!params.action || params.action == 'reportoverview') { + // Go to overview. + this.openReportOverview(parseInt(params.id, 10), courseId, parseInt(params.group, 10), siteId, navCtrl); + } else if (params.action == 'reportdetail') { + this.openUserRetake(parseInt(params.id, 10), parseInt(params.userid, 10), courseId, parseInt(params.try, 10), + siteId, navCtrl); + } + } + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param {string} siteId The site ID. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + if (params.action == 'reportdetail' && !params.userid) { + // Individual details are only available if the teacher is seeing a certain user. + return false; + } + + return this.lessonProvider.isPluginEnabled(); + } + + /** + * Open report overview. + * + * @param {number} moduleId Module ID. + * @param {number} courseId Course ID. + * @param {string} groupId Group ID. + * @param {string} siteId Site ID. + * @param {NavController} [navCtrl] The NavController to use to navigate. + * @return {Promise} Promise resolved when done. + */ + protected openReportOverview(moduleId: number, courseId?: number, groupId?: number, siteId?: string, navCtrl?: NavController) + : Promise { + + const modal = this.domUtils.showModalLoading(); + + // Get the module object. + return this.courseProvider.getModuleBasicInfo(moduleId, siteId).then((module) => { + courseId = courseId || module.course; + + const pageParams = { + module: module, + courseId: Number(courseId), + action: 'report', + group: groupId + }; + + this.linkHelper.goInSite(navCtrl, 'AddonModLessonIndexPage', pageParams, siteId); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error processing link.'); + }).finally(() => { + modal.dismiss(); + }); + } + + /** + * Open a user's retake. + * + * @param {number} moduleId Module ID. + * @param {number} userId User ID. + * @param {number} courseId Course ID. + * @param {number} retake Retake to open. + * @param {string} groupId Group ID. + * @param {string} siteId Site ID. + * @param {NavController} [navCtrl] The NavController to use to navigate. + * @return {Promise} Promise resolved when done. + */ + protected openUserRetake(moduleId: number, userId: number, courseId: number, retake: number, siteId: string, + navCtrl?: NavController): Promise { + + const modal = this.domUtils.showModalLoading(); + + // Get the module object. + return this.courseProvider.getModuleBasicInfo(moduleId, siteId).then((module) => { + courseId = courseId || module.course; + + const pageParams = { + lessonId: module.instance, + courseId: Number(courseId), + userId: userId, + retake: retake || 0 + }; + + this.linkHelper.goInSite(navCtrl, 'AddonModLessonUserRetakePage', pageParams, siteId); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error processing link.'); + }).finally(() => { + modal.dismiss(); + }); + } +} diff --git a/src/addon/mod/lesson/providers/sync-cron-handler.ts b/src/addon/mod/lesson/providers/sync-cron-handler.ts new file mode 100644 index 000000000..a88968db2 --- /dev/null +++ b/src/addon/mod/lesson/providers/sync-cron-handler.ts @@ -0,0 +1,47 @@ +// (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 { CoreCronHandler } from '@providers/cron'; +import { AddonModLessonSyncProvider } from './lesson-sync'; + +/** + * Synchronization cron handler. + */ +@Injectable() +export class AddonModLessonSyncCronHandler implements CoreCronHandler { + name = 'AddonModLessonSyncCronHandler'; + + constructor(private lessonSync: AddonModLessonSyncProvider) {} + + /** + * Execute the process. + * Receives the ID of the site affected, undefined for all sites. + * + * @param {string} [siteId] ID of the site affected, undefined for all sites. + * @return {Promise} Promise resolved when done, rejected if failure. + */ + execute(siteId?: string): Promise { + return this.lessonSync.syncAllLessons(siteId); + } + + /** + * Get the time between consecutive executions. + * + * @return {number} Time between consecutive executions (in ms). + */ + getInterval(): number { + return 600000; + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0d21aa600..caf403c06 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -88,6 +88,7 @@ import { AddonModFeedbackModule } from '@addon/mod/feedback/feedback.module'; import { AddonModFolderModule } from '@addon/mod/folder/folder.module'; import { AddonModForumModule } from '@addon/mod/forum/forum.module'; import { AddonModGlossaryModule } from '@addon/mod/glossary/glossary.module'; +import { AddonModLessonModule } from '@addon/mod/lesson/lesson.module'; import { AddonModPageModule } from '@addon/mod/page/page.module'; import { AddonModQuizModule } from '@addon/mod/quiz/quiz.module'; import { AddonModScormModule } from '@addon/mod/scorm/scorm.module'; @@ -186,6 +187,7 @@ export const CORE_PROVIDERS: any[] = [ AddonModChatModule, AddonModChoiceModule, AddonModLabelModule, + AddonModLessonModule, AddonModResourceModule, AddonModFeedbackModule, AddonModFolderModule, diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index e55e9e287..79902b65b 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -908,6 +908,10 @@ export class CoreCourseHelperProvider { } return promise.then(() => { + // Make sure they're numbers. + courseId = Number(courseId); + sectionId = Number(sectionId); + // Get the site. return this.sitesProvider.getSite(siteId); }).then((s) => {