From 3936438e7f1e19cb5ef12c5e3dbc9d45cf5bdd21 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Fri, 25 May 2018 15:58:29 +0200 Subject: [PATCH] MOBILE-2342 glossary: Implement module, prefetch and link handlers --- src/addon/mod/glossary/glossary.module.ts | 24 +++ .../glossary/providers/entry-link-handler.ts | 77 +++++++ .../glossary/providers/index-link-handler.ts | 30 +++ .../mod/glossary/providers/module-handler.ts | 81 ++++++++ .../glossary/providers/prefetch-handler.ts | 194 ++++++++++++++++++ 5 files changed, 406 insertions(+) create mode 100644 src/addon/mod/glossary/providers/entry-link-handler.ts create mode 100644 src/addon/mod/glossary/providers/index-link-handler.ts create mode 100644 src/addon/mod/glossary/providers/module-handler.ts create mode 100644 src/addon/mod/glossary/providers/prefetch-handler.ts diff --git a/src/addon/mod/glossary/glossary.module.ts b/src/addon/mod/glossary/glossary.module.ts index 6c984079d..424f72bdb 100644 --- a/src/addon/mod/glossary/glossary.module.ts +++ b/src/addon/mod/glossary/glossary.module.ts @@ -13,10 +13,19 @@ // 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 { AddonModGlossaryProvider } from './providers/glossary'; import { AddonModGlossaryOfflineProvider } from './providers/offline'; import { AddonModGlossaryHelperProvider } from './providers/helper'; import { AddonModGlossarySyncProvider } from './providers/sync'; +import { AddonModGlossaryModuleHandler } from './providers/module-handler'; +import { AddonModGlossaryPrefetchHandler } from './providers/prefetch-handler'; +import { AddonModGlossarySyncCronHandler } from './providers/sync-cron-handler'; +import { AddonModGlossaryIndexLinkHandler } from './providers/index-link-handler'; +import { AddonModGlossaryEntryLinkHandler } from './providers/entry-link-handler'; import { AddonModGlossaryComponentsModule } from './components/components.module'; @NgModule({ @@ -30,7 +39,22 @@ import { AddonModGlossaryComponentsModule } from './components/components.module AddonModGlossaryOfflineProvider, AddonModGlossaryHelperProvider, AddonModGlossarySyncProvider, + AddonModGlossaryModuleHandler, + AddonModGlossaryPrefetchHandler, + AddonModGlossarySyncCronHandler, + AddonModGlossaryIndexLinkHandler, + AddonModGlossaryEntryLinkHandler, ] }) export class AddonModGlossaryModule { + constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModGlossaryModuleHandler, + prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModGlossaryPrefetchHandler, + cronDelegate: CoreCronDelegate, syncHandler: AddonModGlossarySyncCronHandler, linksDelegate: CoreContentLinksDelegate, + indexHandler: AddonModGlossaryIndexLinkHandler, discussionHandler: AddonModGlossaryEntryLinkHandler) { + moduleDelegate.registerHandler(moduleHandler); + prefetchDelegate.registerHandler(prefetchHandler); + cronDelegate.register(syncHandler); + linksDelegate.registerHandler(indexHandler); + linksDelegate.registerHandler(discussionHandler); + } } diff --git a/src/addon/mod/glossary/providers/entry-link-handler.ts b/src/addon/mod/glossary/providers/entry-link-handler.ts new file mode 100644 index 000000000..137ccca00 --- /dev/null +++ b/src/addon/mod/glossary/providers/entry-link-handler.ts @@ -0,0 +1,77 @@ +// (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 { 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 { AddonModGlossaryProvider } from './glossary'; + +/** + * Handler to treat links to glossary entries. + */ +@Injectable() +export class AddonModGlossaryEntryLinkHandler extends CoreContentLinksHandlerBase { + name = 'AddonModGlossaryEntryLinkHandler'; + featureName = 'CoreCourseModuleDelegate_AddonModGlossary'; + pattern = /\/mod\/glossary\/showentry\.php.*([\&\?]eid=\d+)/; + + constructor( + private domUtils: CoreDomUtilsProvider, + private linkHelper: CoreContentLinksHelperProvider, + private glossaryProvider: AddonModGlossaryProvider, + private courseHelper: CoreCourseHelperProvider) { + 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 { + return [{ + action: (siteId, navCtrl?): void => { + const modal = this.domUtils.showModalLoading(); + const entryId = parseInt(params.eid, 10); + let promise; + + if (courseId) { + promise = Promise.resolve(courseId); + } else { + promise = this.glossaryProvider.getEntry(entryId, siteId).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true); + + return Promise.reject(null); + }).then((entry) => { + return this.courseHelper.getModuleCourseIdByInstance(entry.glossaryid, 'glossary', siteId); + }); + } + + return promise.then((courseId) => { + this.linkHelper.goInSite(navCtrl, 'AddonModGlossaryEntryPage', {courseId, entryId}, siteId); + }).finally(() => { + modal.dismiss(); + }); + } + }]; + } +} diff --git a/src/addon/mod/glossary/providers/index-link-handler.ts b/src/addon/mod/glossary/providers/index-link-handler.ts new file mode 100644 index 000000000..d71be0005 --- /dev/null +++ b/src/addon/mod/glossary/providers/index-link-handler.ts @@ -0,0 +1,30 @@ +// (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 { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { AddonModGlossaryProvider } from './glossary'; + +/** + * Handler to treat links to glossary index. + */ +@Injectable() +export class AddonModGlossaryIndexLinkHandler extends CoreContentLinksModuleIndexHandler { + name = 'AddonModGlossaryIndexLinkHandler'; + + constructor(courseHelper: CoreCourseHelperProvider, protected glossaryProvider: AddonModGlossaryProvider) { + super(courseHelper, 'AddonModGlossary', 'glossary'); + } +} diff --git a/src/addon/mod/glossary/providers/module-handler.ts b/src/addon/mod/glossary/providers/module-handler.ts new file mode 100644 index 000000000..ce81f445b --- /dev/null +++ b/src/addon/mod/glossary/providers/module-handler.ts @@ -0,0 +1,81 @@ +// (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 { AddonModGlossaryIndexComponent } from '../components/index/index'; +import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate'; +import { CoreCourseProvider } from '@core/course/providers/course'; + +/** + * Handler to support glossary modules. + */ +@Injectable() +export class AddonModGlossaryModuleHandler implements CoreCourseModuleHandler { + name = 'AddonModGlossary'; + modName = 'glossary'; + + constructor(private courseProvider: CoreCourseProvider) { } + + /** + * Check if the handler is enabled on a site level. + * + * @return {boolean} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean { + return true; + } + + /** + * 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('glossary'), + title: module.name, + class: 'addon-mod_glossary-handler', + showDownloadButton: true, + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { + navCtrl.push('AddonModGlossaryIndexPage', {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 AddonModGlossaryIndexComponent; + } + + /** + * Whether to display the course refresher in single activity course format. If it returns false, a refresher must be + * included in the template that calls the doRefresh method of the component. Defaults to true. + * + * @return {boolean} Whether the refresher should be displayed. + */ + displayRefresherInSingleActivity(): boolean { + return false; + } +} diff --git a/src/addon/mod/glossary/providers/prefetch-handler.ts b/src/addon/mod/glossary/providers/prefetch-handler.ts new file mode 100644 index 000000000..16e088851 --- /dev/null +++ b/src/addon/mod/glossary/providers/prefetch-handler.ts @@ -0,0 +1,194 @@ +// (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, Injector } from '@angular/core'; +import { CoreCourseModulePrefetchHandlerBase } from '@core/course/classes/module-prefetch-handler'; +import { CoreUserProvider } from '@core/user/providers/user'; +import { AddonModGlossaryProvider } from './glossary'; +import { CoreConstants } from '@core/constants'; + +/** + * Handler to prefetch forums. + */ +@Injectable() +export class AddonModGlossaryPrefetchHandler extends CoreCourseModulePrefetchHandlerBase { + name = 'AddonModGlossary'; + modName = 'glossary'; + component = AddonModGlossaryProvider.COMPONENT; + updatesNames = /^configuration$|^.*files$|^entries$/; + + constructor(injector: Injector, + private userProvider: CoreUserProvider, + private glossaryProvider: AddonModGlossaryProvider) { + super(injector); + } + + /** + * Return the status to show based on current status. E.g. a module might want to show outdated instead of downloaded. + * If not implemented, the original status will be returned. + * + * @param {any} module Module. + * @param {string} status The current status. + * @param {boolean} canCheck Whether the site allows checking for updates. + * @return {string} Status to display. + */ + determineStatus(module: any, status: string, canCheck: boolean): string { + if (!canCheck && status === CoreConstants.DOWNLOADED) { + /* Glossary are always marked as outdated if updates cannot be checked because we can't tell if there's something + new without having to call all the WebServices. */ + return CoreConstants.OUTDATED; + } else { + return status; + } + } + + /** + * Download the module. + * + * @param {any} module The module object returned by WS. + * @param {number} courseId Course ID. + * @param {string} [dirPath] Path of the directory where to store all the content files. @see downloadOrPrefetch. + * @return {Promise} Promise resolved when all content is downloaded. + */ + download(module: any, courseId: number, dirPath?: string): Promise { + // Glossaries cannot be downloaded right away, only prefetched. + return this.prefetch(module, courseId); + } + + /** + * Get list of files. If not defined, we'll assume they're in module.contents. + * + * @param {any} module Module. + * @param {Number} courseId Course ID the module belongs to. + * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. + * @return {Promise} Promise resolved with the list of files. + */ + getFiles(module: any, courseId: number, single?: boolean): Promise { + return this.glossaryProvider.getGlossary(courseId, module.id).then((glossary) => { + return this.glossaryProvider.fetchAllEntries(this.glossaryProvider.getEntriesByLetter, [glossary.id, 'ALL']) + .then((entries) => { + return this.getFilesFromGlossaryAndEntries(module, glossary, entries); + }); + }).catch(() => { + // Glossary not found, return empty list. + return []; + }); + } + + /** + * Get the list of downloadable files. It includes entry embedded files. + * + * @param {any} module Module to get the files. + * @param {any} glossary Glossary + * @param {any[]} entries Entries of the Glossary. + * @return {any[]} List of Files. + */ + protected getFilesFromGlossaryAndEntries(module: any, glossary: any, entries: any[]): any[] { + let files = this.getIntroFilesFromInstance(module, glossary); + // Get entries files. + entries.forEach((entry) => { + files = files.concat(this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(entry.definition)); + files = files.concat(entry.attachments); + }); + + return files; + } + + /** + * Invalidate the prefetched content. + * + * @param {number} moduleId The module ID. + * @param {number} courseId The course ID the module belongs to. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateContent(moduleId: number, courseId: number): Promise { + return this.glossaryProvider.invalidateContent(moduleId, courseId); + } + + /** + * Prefetch a module. + * + * @param {any} module Module. + * @param {number} courseId Course ID the module belongs to. + * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. + * @param {string} [dirPath] Path of the directory where to store all the content files. @see downloadOrPrefetch. + * @return {Promise} Promise resolved when done. + */ + prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise { + return this.prefetchPackage(module, courseId, single, this.prefetchGlossary.bind(this)); + } + + /** + * Prefetch a glossary. + * + * @param {any} module The module object returned by WS. + * @param {number} courseId Course ID the module belongs to. + * @param {boolean} single True if we're downloading a single module, false if we're downloading a whole section. + * @param {string} siteId Site ID. + * @return {Promise} Promise resolved when done. + */ + protected prefetchGlossary(module: any, courseId: number, single: boolean, siteId: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + // Prefetch the glossary data. + return this.glossaryProvider.getGlossary(courseId, module.id, siteId).then((glossary) => { + const promises = []; + + glossary.browsemodes.forEach((mode) => { + switch (mode) { + case 'letter': // Always done. Look bellow. + break; + case 'cat': // Not implemented. + promises.push(this.glossaryProvider.fetchAllEntries(this.glossaryProvider.getEntriesByCategory, + [glossary.id, AddonModGlossaryProvider.SHOW_ALL_CATERGORIES], false, siteId)); + break; + case 'date': + promises.push(this.glossaryProvider.fetchAllEntries(this.glossaryProvider.getEntriesByDate, + [glossary.id, 'CREATION', 'DESC'], false, siteId)); + promises.push(this.glossaryProvider.fetchAllEntries(this.glossaryProvider.getEntriesByDate, + [glossary.id, 'UPDATE', 'DESC'], false, siteId)); + break; + case 'author': + promises.push(this.glossaryProvider.fetchAllEntries(this.glossaryProvider.getEntriesByAuthor, + [glossary.id, 'ALL', 'LASTNAME', 'ASC'], false, siteId)); + break; + default: + } + }); + + // Fetch all entries to get information from. + promises.push(this.glossaryProvider.fetchAllEntries(this.glossaryProvider.getEntriesByLetter, + [glossary.id, 'ALL'], false, siteId).then((entries) => { + const promises = []; + const userIds = []; + + // Fetch user avatars. + entries.forEach((entry) => { + // Fetch individual entries. + promises.push(this.glossaryProvider.getEntry(entry.id, siteId)); + + userIds.push(entry.userid); + }); + + // Prefetch user profiles. + promises.push(this.userProvider.prefetchProfiles(userIds, courseId, siteId)); + + const files = this.getFilesFromGlossaryAndEntries(module, glossary, entries); + promises.push(this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id)); + + return Promise.all(promises); + })); + }); + } +}