From b2c4b65024914c85fe59a3593bc004b2eadac6fa Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 23 Feb 2018 15:53:45 +0100 Subject: [PATCH] MOBILE-2333 siteaddons: Support course options site addon --- src/core/course/pages/section/section.html | 2 +- src/core/course/pages/section/section.ts | 8 +- src/core/course/providers/options-delegate.ts | 6 + .../classes/module-prefetch-handler.ts | 67 +-------- .../components/components.module.ts | 10 +- .../course-option/course-option.html | 6 + .../components/course-option/course-option.ts | 66 +++++++++ src/core/siteaddons/providers/helper.ts | 140 +++++++++++++++--- src/core/siteaddons/providers/siteaddons.ts | 93 +++++++++++- 9 files changed, 303 insertions(+), 95 deletions(-) create mode 100644 src/core/siteaddons/components/course-option/course-option.html create mode 100644 src/core/siteaddons/components/course-option/course-option.ts diff --git a/src/core/course/pages/section/section.html b/src/core/course/pages/section/section.html index 83bf61d17..56cd025b4 100644 --- a/src/core/course/pages/section/section.html +++ b/src/core/course/pages/section/section.html @@ -30,7 +30,7 @@ - + diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts index 0e0ced8b7..093e0a29f 100644 --- a/src/core/course/pages/section/section.ts +++ b/src/core/course/pages/section/section.ts @@ -45,7 +45,6 @@ export class CoreCourseSectionPage implements OnDestroy { sectionId: number; sectionNumber: number; courseHandlers: CoreCourseOptionsHandlerToDisplay[]; - handlerData: any = {}; // Data to send to the handlers components. dataLoaded: boolean; downloadEnabled: boolean; downloadEnabledIcon = 'square-outline'; // Disabled by default. @@ -70,7 +69,6 @@ export class CoreCourseSectionPage implements OnDestroy { this.sectionId = navParams.get('sectionId'); this.sectionNumber = navParams.get('sectionNumber'); this.module = navParams.get('module'); - this.handlerData.courseId = this.course.id; // Get the title to display. We dont't have sections yet. this.title = courseFormatDelegate.getCourseTitle(this.course); @@ -194,6 +192,12 @@ export class CoreCourseSectionPage implements OnDestroy { // Load the course handlers. promises.push(this.courseOptionsDelegate.getHandlersToDisplay(this.course, refresh, false).then((handlers) => { + // Add the courseId to the handler component data. + handlers.forEach((handler) => { + handler.data.componentData = handler.data.componentData || {}; + handler.data.componentData.courseId = this.course.id; + }); + this.courseHandlers = handlers; })); diff --git a/src/core/course/providers/options-delegate.ts b/src/core/course/providers/options-delegate.ts index 35182a369..e73a4b6ab 100644 --- a/src/core/course/providers/options-delegate.ts +++ b/src/core/course/providers/options-delegate.ts @@ -90,6 +90,12 @@ export interface CoreCourseOptionsHandlerData { * When the component is created, it will receive the courseId as input. */ component: any; + + /** + * Data to pass to the component. All the properties in this object will be passed to the component as inputs. + * @type {any} + */ + componentData?: any; } /** diff --git a/src/core/siteaddons/classes/module-prefetch-handler.ts b/src/core/siteaddons/classes/module-prefetch-handler.ts index f87750000..31c502892 100644 --- a/src/core/siteaddons/classes/module-prefetch-handler.ts +++ b/src/core/siteaddons/classes/module-prefetch-handler.ts @@ -80,48 +80,8 @@ export class CoreSiteAddonsModulePrefetchHandler extends CoreCourseModulePrefetc promises.push(this.downloadOrPrefetchFiles(site.id, module, courseId, prefetch, dirPath)); // Call all the offline functions. - for (const method in this.handlerSchema.offlinefunctions) { - if (site.wsAvailable(method)) { - // The method is a WS. - const paramsList = this.handlerSchema.offlinefunctions[method], - cacheKey = this.siteAddonsProvider.getCallWSCacheKey(method, args); - let params = {}; - - if (!paramsList.length) { - // No params defined, send the default ones. - params = args; - } else { - for (const i in paramsList) { - const paramName = paramsList[i]; - - if (typeof args[paramName] != 'undefined') { - params[paramName] = args[paramName]; - } else { - // The param is not one of the default ones. Try to calculate the param to use. - const value = this.getDownloadParam(module, courseId, paramName); - if (typeof value != 'undefined') { - params[paramName] = value; - } - } - } - } - - promises.push(this.siteAddonsProvider.callWS(method, params, {cacheKey: cacheKey})); - } else { - // It's a method to get content. - promises.push(this.siteAddonsProvider.getContent(this.component, method, args).then((result) => { - const subPromises = []; - - // Prefetch the files in the content. - if (result.files && result.files.length) { - subPromises.push(this.filepoolProvider.downloadOrPrefetchFiles(siteId, result.files, prefetch, false, - this.component, module.id, dirPath)); - } - - return Promise.all(subPromises); - })); - } - } + promises.push(this.siteAddonsProvider.prefetchFunctions(this.component, args, this.handlerSchema, courseId, + module, prefetch, dirPath, site)); return Promise.all(promises); }); @@ -165,29 +125,6 @@ export class CoreSiteAddonsModulePrefetchHandler extends CoreCourseModulePrefetc }); } - /** - * Get the value of a WS param for prefetch. - * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @param {string} paramName Name of the param as defined by the handler. - * @return {any} The value. - */ - protected getDownloadParam(module: any, courseId: number, paramName: string): any { - switch (paramName) { - case 'courseids': - // The WS needs the list of course IDs. Create the list. - return [courseId]; - - case this.component + 'id': - // The WS needs the instance id. - return module.instance; - - default: - // No more params supported for now. - } - } - /** * Invalidate the prefetched content. * diff --git a/src/core/siteaddons/components/components.module.ts b/src/core/siteaddons/components/components.module.ts index 200e8be8f..61021a6b9 100644 --- a/src/core/siteaddons/components/components.module.ts +++ b/src/core/siteaddons/components/components.module.ts @@ -20,11 +20,13 @@ import { CoreComponentsModule } from '../../../components/components.module'; import { CoreCompileHtmlComponentModule } from '../../compile/components/compile-html/compile-html.module'; import { CoreSiteAddonsAddonContentComponent } from './addon-content/addon-content'; import { CoreSiteAddonsModuleIndexComponent } from './module-index/module-index'; +import { CoreSiteAddonsCourseOptionComponent } from './course-option/course-option'; @NgModule({ declarations: [ CoreSiteAddonsAddonContentComponent, - CoreSiteAddonsModuleIndexComponent + CoreSiteAddonsModuleIndexComponent, + CoreSiteAddonsCourseOptionComponent ], imports: [ CommonModule, @@ -37,10 +39,12 @@ import { CoreSiteAddonsModuleIndexComponent } from './module-index/module-index' ], exports: [ CoreSiteAddonsAddonContentComponent, - CoreSiteAddonsModuleIndexComponent + CoreSiteAddonsModuleIndexComponent, + CoreSiteAddonsCourseOptionComponent ], entryComponents: [ - CoreSiteAddonsModuleIndexComponent + CoreSiteAddonsModuleIndexComponent, + CoreSiteAddonsCourseOptionComponent ] }) export class CoreSiteAddonsComponentsModule {} diff --git a/src/core/siteaddons/components/course-option/course-option.html b/src/core/siteaddons/components/course-option/course-option.html new file mode 100644 index 000000000..ada863f5e --- /dev/null +++ b/src/core/siteaddons/components/course-option/course-option.html @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/core/siteaddons/components/course-option/course-option.ts b/src/core/siteaddons/components/course-option/course-option.ts new file mode 100644 index 000000000..99a26249a --- /dev/null +++ b/src/core/siteaddons/components/course-option/course-option.ts @@ -0,0 +1,66 @@ +// (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 { Component, OnInit, Input, ViewChild } from '@angular/core'; +import { CoreSiteAddonsProvider } from '../../providers/siteaddons'; +import { CoreSiteAddonsAddonContentComponent } from '../addon-content/addon-content'; + +/** + * Component that displays the index of a course option site addon. + */ +@Component({ + selector: 'core-site-addons-course-option', + templateUrl: 'course-option.html', +}) +export class CoreSiteAddonsCourseOptionComponent implements OnInit { + @Input() courseId: number; + @Input() handlerUniqueName: string; + + @ViewChild(CoreSiteAddonsAddonContentComponent) content: CoreSiteAddonsAddonContentComponent; + + component: string; + method: string; + args: any; + bootstrapResult: any; + + constructor(protected siteAddonsProvider: CoreSiteAddonsProvider) { } + + /** + * Component being initialized. + */ + ngOnInit(): void { + if (this.handlerUniqueName) { + const handler = this.siteAddonsProvider.getSiteAddonHandler(this.handlerUniqueName); + if (handler) { + this.component = handler.addon.component; + this.method = handler.handlerSchema.method; + this.args = { + courseid: this.courseId, + }; + this.bootstrapResult = handler.bootstrapResult; + } + } + } + + /** + * Refresh the data. + * + * @param {any} refresher Refresher. + */ + refreshData(refresher: any): void { + this.content.refreshData().finally(() => { + refresher.complete(); + }); + } +} diff --git a/src/core/siteaddons/providers/helper.ts b/src/core/siteaddons/providers/helper.ts index 2869aca16..66885636a 100644 --- a/src/core/siteaddons/providers/helper.ts +++ b/src/core/siteaddons/providers/helper.ts @@ -24,9 +24,13 @@ import { CoreCourseModuleDelegate, CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '../../course/providers/module-delegate'; import { CoreCourseModulePrefetchDelegate } from '../../course/providers/module-prefetch-delegate'; +import { + CoreCourseOptionsDelegate, CoreCourseOptionsHandler, CoreCourseOptionsHandlerData +} from '../../course/providers/options-delegate'; import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '../../user/providers/user-delegate'; import { CoreDelegateHandler } from '../../../classes/delegate'; import { CoreSiteAddonsModuleIndexComponent } from '../components/module-index/module-index'; +import { CoreSiteAddonsCourseOptionComponent } from '../components/course-option/course-option'; import { CoreSiteAddonsProvider } from './siteaddons'; import { CoreSiteAddonsModulePrefetchHandler } from '../classes/module-prefetch-handler'; import { CoreCompileProvider } from '../../compile/providers/compile'; @@ -47,7 +51,7 @@ export class CoreSiteAddonsHelperProvider { private userDelegate: CoreUserDelegate, private langProvider: CoreLangProvider, private siteAddonsProvider: CoreSiteAddonsProvider, private prefetchDelegate: CoreCourseModulePrefetchDelegate, private compileProvider: CoreCompileProvider, private utils: CoreUtilsProvider, - private coursesProvider: CoreCoursesProvider) { + private coursesProvider: CoreCoursesProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate) { this.logger = logger.getInstance('CoreSiteAddonsHelperProvider'); } @@ -130,6 +134,54 @@ export class CoreSiteAddonsHelperProvider { return this.getHandlerPrefixForStrings(handlerName) + key; } + /** + * Check if a handler is enabled for a certain course. + * + * @param {number} courseId Course ID to check. + * @param {boolean} [restrictEnrolled] If true or undefined, handler is only enabled for courses the user is enrolled in. + * @param {any} [restrict] Users and courses the handler is restricted to. + * @return {boolean | Promise} Whether the handler is enabled. + */ + protected isHandlerEnabledForCourse(courseId: number, restrictEnrolled?: boolean, restrict?: any): boolean | Promise { + if (restrict && restrict.courses && restrict.courses.indexOf(courseId) == -1) { + // Course is not in the list of restricted courses. + return false; + } + + if (restrictEnrolled || typeof restrictEnrolled == 'undefined') { + // Only enabled for courses the user is enrolled to. Check if the user is enrolled in the course. + return this.coursesProvider.getUserCourse(courseId, true).then(() => { + return true; + }).catch(() => { + return false; + }); + } + + return true; + } + + /** + * Check if a handler is enabled for a certain user. + * + * @param {number} userId User ID to check. + * @param {boolean} [restrictCurrent] Whether handler is only enabled for current user. + * @param {any} [restrict] Users and courses the handler is restricted to. + * @return {boolean} Whether the handler is enabled. + */ + protected isHandlerEnabledForUser(userId: number, restrictCurrent?: boolean, restrict?: any): boolean { + if (restrictCurrent && userId != this.sitesProvider.getCurrentSite().getUserId()) { + // Only enabled for current user. + return false; + } + + if (restrict && restrict.users && restrict.users.indexOf(userId) == -1) { + // User is not in the list of restricted users. + return false; + } + + return true; + } + /** * Check if a certain addon is a site addon and it's enabled in a certain site. * @@ -227,6 +279,11 @@ export class CoreSiteAddonsHelperProvider { result.restrict); break; + case 'CoreCourseOptionsDelegate': + uniqueName = this.registerCourseOptionHandler(addon, handlerName, handlerSchema, result.jsResult, + result.restrict); + break; + default: // Nothing to do. } @@ -243,6 +300,60 @@ export class CoreSiteAddonsHelperProvider { }); } + /** + * Given a handler in an addon, register it in the course options delegate. + * + * @param {any} addon Data of the addon. + * @param {string} handlerName Name of the handler in the addon. + * @param {any} handlerSchema Data about the handler. + * @param {any} [bootstrapResult] Result of executing the bootstrap JS. + * @param {any} [restrict] List of users and courses the handler is restricted to. + * @return {string} A string to identify the handler. + */ + protected registerCourseOptionHandler(addon: any, handlerName: string, handlerSchema: any, bootstrapResult?: any, + restrict?: any): string { + if (!handlerSchema || !handlerSchema.displaydata) { + // Required data not provided, stop. + return; + } + + // Create the base handler. + const uniqueName = this.siteAddonsProvider.getHandlerUniqueName(addon, handlerName), + baseHandler = this.getBaseHandler(uniqueName), + prefixedTitle = this.getHandlerPrefixedString(baseHandler.name, handlerSchema.displaydata.title); + let handler: CoreCourseOptionsHandler; + + // Extend the base handler, adding the properties required by the delegate. + handler = Object.assign(baseHandler, { + priority: handlerSchema.priority, + isEnabledForCourse: (courseId: number, accessData: any, navOptions?: any, admOptions?: any) + : boolean | Promise => { + return this.isHandlerEnabledForCourse(courseId, handlerSchema.restricttoenrolledcourses, restrict); + }, + getDisplayData: (courseId: number): CoreCourseOptionsHandlerData => { + return { + title: prefixedTitle, + class: handlerSchema.displaydata.class, + component: CoreSiteAddonsCourseOptionComponent, + componentData: { + handlerUniqueName: uniqueName + } + }; + }, + prefetch: (course: any): Promise => { + const args = { + courseid: course.id, + }; + + return this.siteAddonsProvider.prefetchFunctions(addon.component, args, handlerSchema, course.id, undefined, true); + } + }); + + this.courseOptionsDelegate.registerHandler(handler); + + return uniqueName; + } + /** * Given a handler in an addon, register it in the main menu delegate. * @@ -378,31 +489,14 @@ export class CoreSiteAddonsHelperProvider { priority: handlerSchema.priority, type: handlerSchema.type, isEnabledForUser: (user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise => { - if (handlerSchema.restricttocurrentuser && user.id != this.sitesProvider.getCurrentSite().getUserId()) { - // Only enabled for current user. + // First check if it's enabled for the user. + const enabledForUser = this.isHandlerEnabledForUser(user.id, handlerSchema.restricttocurrentuser, restrict); + if (!enabledForUser) { return false; } - if (restrict) { - if (restrict.users && restrict.users.indexOf(user.id) == -1) { - // User is not in the list of restricted users. - return false; - } else if (restrict.courses && restrict.courses.indexOf(courseId) == -1) { - // Course is not in the list of restricted courses. - return false; - } - } - - if (handlerSchema.restricttoenrolledcourses || typeof handlerSchema.restricttoenrolledcourses == 'undefined') { - // Only enabled for courses the user is enrolled to. Check if the user is enrolled in the course. - return this.coursesProvider.getUserCourse(courseId, true).then(() => { - return true; - }).catch(() => { - return false; - }); - } - - return true; + // Enabled for user, check if it's enabled for the course. + return this.isHandlerEnabledForCourse(courseId, handlerSchema.restricttoenrolledcourses, restrict); }, getDisplayData: (user: any, courseId: number): CoreUserProfileHandlerData => { return { diff --git a/src/core/siteaddons/providers/siteaddons.ts b/src/core/siteaddons/providers/siteaddons.ts index dd3bd05ae..94fca9258 100644 --- a/src/core/siteaddons/providers/siteaddons.ts +++ b/src/core/siteaddons/providers/siteaddons.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { Platform } from 'ionic-angular'; import { CoreAppProvider } from '../../../providers/app'; +import { CoreFilepoolProvider } from '../../../providers/filepool'; import { CoreLangProvider } from '../../../providers/lang'; import { CoreLoggerProvider } from '../../../providers/logger'; import { CoreSite, CoreSiteWSPreSets } from '../../../classes/site'; @@ -62,7 +63,8 @@ export class CoreSiteAddonsProvider { protected siteAddons: {[name: string]: CoreSiteAddonsHandler} = {}; // Site addons registered. constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, - private langProvider: CoreLangProvider, private appProvider: CoreAppProvider, private platform: Platform) { + private langProvider: CoreLangProvider, private appProvider: CoreAppProvider, private platform: Platform, + private filepoolProvider: CoreFilepoolProvider) { this.logger = logger.getInstance('CoreUserProvider'); } @@ -207,6 +209,30 @@ export class CoreSiteAddonsProvider { return this.ROOT_CACHE_KEY + 'content:' + component + ':' + method + ':' + this.utils.sortAndStringify(args); } + /** + * Get the value of a WS param for prefetch. + * + * @param {string} component The component of the handler. + * @param {string} paramName Name of the param as defined by the handler. + * @param {number} [courseId] Course ID (if prefetching a course). + * @param {any} [module] The module object returned by WS (if prefetching a module). + * @return {any} The value. + */ + protected getDownloadParam(component: string, paramName: string, courseId?: number, module?: any): any { + switch (paramName) { + case 'courseids': + // The WS needs the list of course IDs. Create the list. + return [courseId]; + + case component + 'id': + // The WS needs the instance id. + return module && module.instance; + + default: + // No more params supported for now. + } + } + /** * Get the unique name of a handler (addon + handler). * @@ -320,6 +346,71 @@ export class CoreSiteAddonsProvider { return args; } + /** + * Prefetch offline functions for a site addon handler. + * + * @param {string} component The component of the handler. + * @param {any} args Params to send to the get_content calls. + * @param {any} handlerSchema The handler schema. + * @param {number} [courseId] Course ID (if prefetching a course). + * @param {any} [module] The module object returned by WS (if prefetching a module). + * @param {boolean} [prefetch] True to prefetch, false to download right away. + * @param {string} [dirPath] Path of the directory where to store all the content files. + * @param {CoreSite} [site] Site. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + prefetchFunctions(component: string, args: any, handlerSchema: any, courseId?: number, module?: any, prefetch?: boolean, + dirPath?: string, site?: CoreSite): Promise { + site = site || this.sitesProvider.getCurrentSite(); + + const promises = []; + + for (const method in handlerSchema.offlinefunctions) { + if (site.wsAvailable(method)) { + // The method is a WS. + const paramsList = handlerSchema.offlinefunctions[method], + cacheKey = this.getCallWSCacheKey(method, args); + let params = {}; + + if (!paramsList.length) { + // No params defined, send the default ones. + params = args; + } else { + for (const i in paramsList) { + const paramName = paramsList[i]; + + if (typeof args[paramName] != 'undefined') { + params[paramName] = args[paramName]; + } else { + // The param is not one of the default ones. Try to calculate the param to use. + const value = this.getDownloadParam(component, paramName, courseId, module); + if (typeof value != 'undefined') { + params[paramName] = value; + } + } + } + } + + promises.push(this.callWS(method, params, {cacheKey: cacheKey})); + } else { + // It's a method to get content. + promises.push(this.getContent(component, method, args).then((result) => { + const subPromises = []; + + // Prefetch the files in the content. + if (result.files && result.files.length) { + subPromises.push(this.filepoolProvider.downloadOrPrefetchFiles(site.id, result.files, prefetch, false, + component, module.id, dirPath)); + } + + return Promise.all(subPromises); + })); + } + } + + return Promise.all(promises); + } + /** * Store a site addon handler. *