MOBILE-2333 siteaddons: Support course options site addon

main
Dani Palou 2018-02-23 15:53:45 +01:00
parent 141f979cea
commit b2c4b65024
9 changed files with 303 additions and 95 deletions

View File

@ -30,7 +30,7 @@
<!-- One tab per handler. -->
<core-tab *ngFor="let handler of courseHandlers" [title]="handler.data.title | translate" class="{{handler.data.class}}">
<ng-template>
<core-dynamic-component [component]="handler.data.component" [data]="handlerData"></core-dynamic-component>
<core-dynamic-component [component]="handler.data.component" [data]="handler.data.componentData"></core-dynamic-component>
</ng-template>
</core-tab>
</core-tabs>

View File

@ -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;
}));

View File

@ -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;
}
/**

View File

@ -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.
*

View File

@ -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 {}

View File

@ -0,0 +1,6 @@
<ion-content>
<ion-refresher [enabled]="content && content.dataLoaded" (ionRefresh)="refreshData($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-site-addons-addon-content *ngIf="component && method" [component]="component" [method]="method" [args]="args" [bootstrapResult]="bootstrapResult"></core-site-addons-addon-content>
</ion-content>

View File

@ -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();
});
}
}

View File

@ -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<boolean>} Whether the handler is enabled.
*/
protected isHandlerEnabledForCourse(courseId: number, restrictEnrolled?: boolean, restrict?: any): boolean | Promise<boolean> {
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<boolean> => {
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<any> => {
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<boolean> => {
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 {

View File

@ -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<any>} Promise resolved when done.
*/
prefetchFunctions(component: string, args: any, handlerSchema: any, courseId?: number, module?: any, prefetch?: boolean,
dirPath?: string, site?: CoreSite): Promise<any> {
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.
*