MOBILE-3664 siteplugins: Implement handlers

main
Dani Palou 2021-03-02 16:21:15 +01:00
parent 1659df55cb
commit 2040840d79
21 changed files with 1442 additions and 7 deletions

View File

@ -101,7 +101,7 @@ export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent i
if (modName === 'resources') { if (modName === 'resources') {
icon = CoreCourse.getModuleIconSrc('page', modIcons['page']); icon = CoreCourse.getModuleIconSrc('page', modIcons['page']);
} else { } else {
icon = CoreCourseModuleDelegate.getModuleIconSrc(modName, modIcons[modName]); icon = CoreCourseModuleDelegate.getModuleIconSrc(modName, modIcons[modName]) || '';
} }
this.entries.push({ this.entries.push({

View File

@ -70,7 +70,7 @@ export interface AddonModQuizAccessRuleHandler extends CoreDelegateHandler {
* *
* @return The component (or promise resolved with component) to use, undefined if not found. * @return The component (or promise resolved with component) to use, undefined if not found.
*/ */
getPreflightComponent?(): Type<unknown> | Promise<Type<unknown>>; getPreflightComponent?(): undefined | Type<unknown> | Promise<Type<unknown>>;
/** /**
* Function called when the preflight check has passed. This is a chance to record that fact in some way. * Function called when the preflight check has passed. This is a chance to record that fact in some way.

View File

@ -82,7 +82,7 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler {
* *
* @return The icon src. * @return The icon src.
*/ */
getIconSrc?(): string; getIconSrc?(): string | undefined;
/** /**
* Check if this type of module supports a certain feature. * Check if this type of module supports a certain feature.
@ -336,7 +336,7 @@ export class CoreCourseModuleDelegateService extends CoreDelegate<CoreCourseModu
* @param modicon The mod icon string. * @param modicon The mod icon string.
* @return The icon src. * @return The icon src.
*/ */
getModuleIconSrc(modname: string, modicon?: string): string { getModuleIconSrc(modname: string, modicon?: string): string | undefined {
return this.executeFunctionOnEnabled<string>(modname, 'getIconSrc') || return this.executeFunctionOnEnabled<string>(modname, 'getIconSrc') ||
CoreCourse.getModuleIconSrc(modname, modicon); CoreCourse.getModuleIconSrc(modname, modicon);
} }

View File

@ -18,7 +18,7 @@ import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { CoreStatusWithWarningsWSResponse, CoreWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; import { CoreStatusWithWarningsWSResponse, CoreWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
import { CoreEvents } from '@singletons/events'; import { CoreEvents, CoreEventSiteData } from '@singletons/events';
import { CoreWSError } from '@classes/errors/wserror'; import { CoreWSError } from '@classes/errors/wserror';
const ROOT_CACHE_KEY = 'mmCourses:'; const ROOT_CACHE_KEY = 'mmCourses:';
@ -853,7 +853,7 @@ export class CoreCoursesProvider {
if (added.length || removed.length) { if (added.length || removed.length) {
// At least 1 course was added or removed, trigger the event. // At least 1 course was added or removed, trigger the event.
CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_CHANGED, { CoreEvents.trigger<CoreCoursesMyCoursesChangedEventData>(CoreCoursesProvider.EVENT_MY_COURSES_CHANGED, {
added: added, added: added,
removed: removed, removed: removed,
}, site.getId()); }, site.getId());
@ -1169,7 +1169,7 @@ export const CoreCourses = makeSingleton(CoreCoursesProvider);
/** /**
* Data sent to the EVENT_MY_COURSES_UPDATED. * Data sent to the EVENT_MY_COURSES_UPDATED.
*/ */
export type CoreCoursesMyCoursesUpdatedEventData = { export type CoreCoursesMyCoursesUpdatedEventData = CoreEventSiteData & {
action: string; // Action performed. action: string; // Action performed.
courseId?: number; // Course ID affected (if any). courseId?: number; // Course ID affected (if any).
course?: CoreCourseAnyCourseData; // Course affected (if any). course?: CoreCourseAnyCourseData; // Course affected (if any).
@ -1177,6 +1177,14 @@ export type CoreCoursesMyCoursesUpdatedEventData = {
value?: boolean; // The new value for the state changed. value?: boolean; // The new value for the state changed.
}; };
/**
* Data sent to the EVENT_MY_COURSES_CHANGED.
*/
export type CoreCoursesMyCoursesChangedEventData = CoreEventSiteData & {
added: number[];
removed: number[];
};
/** /**
* Params of core_enrol_get_users_courses WS. * Params of core_enrol_get_users_courses WS.
*/ */

View File

@ -0,0 +1,75 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { CoreUtils } from '@services/utils/utils';
import { CoreSitePlugins, CoreSitePluginsInitHandlerData } from '../services/siteplugins';
/**
* Base class for components that will display a component using core-compile-html and want to call a
* componentInit function returned by the handler JS.
*/
export class CoreSitePluginsCompileInitComponent {
content = ''; // Content.
jsData: Record<string, unknown> = {}; // Data to pass to the component.
protected handlerSchema?: CoreSitePluginsInitHandlerData; // The handler data.
/**
* Function called when the component is created.
*
* @param instance The component instance.
*/
componentCreated(instance: unknown): void {
// Check if the JS defined an init function.
if (instance && this.handlerSchema?.methodJSResult?.componentInit) {
this.handlerSchema.methodJSResult.componentInit.apply(instance);
}
}
/**
* Get the handler data.
*
* @param name The name of the handler.
*/
getHandlerData(name: string): void {
// Retrieve the handler data.
const handler = CoreSitePlugins.getSitePluginHandler(name);
this.handlerSchema = handler?.handlerSchema;
if (!this.handlerSchema) {
return;
}
// Load first template.
if (this.handlerSchema.methodTemplates?.length) {
this.content = this.handlerSchema.methodTemplates[0].html;
this.jsData.CONTENT_TEMPLATES = CoreUtils.objectToKeyValueMap(
this.handlerSchema.methodTemplates,
'id',
'html',
);
}
// Pass data from the method result to the component.
if (this.handlerSchema.methodOtherdata) {
this.jsData.CONTENT_OTHERDATA = this.handlerSchema.methodOtherdata;
}
if (this.handlerSchema.methodJSResult) {
this.jsData.CONTENT_JS_RESULT = this.handlerSchema.methodJSResult;
}
}
}

View File

@ -0,0 +1,55 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Type } from '@angular/core';
import { AddonModAssignDefaultFeedbackHandler } from '@addons/mod/assign/services/handlers/default-feedback';
import { AddonModAssignPlugin } from '@addons/mod/assign/services/assign';
import { CoreSitePluginsAssignFeedbackComponent } from '@features/siteplugins/components/assign-feedback/assign-feedback';
import { Translate } from '@singletons';
/**
* Handler to display an assign feedback site plugin.
*/
export class CoreSitePluginsAssignFeedbackHandler extends AddonModAssignDefaultFeedbackHandler {
constructor(public name: string, public type: string, protected prefix: string) {
super();
}
/**
* @inheritdoc
*/
getComponent(): Type<unknown> | undefined {
return CoreSitePluginsAssignFeedbackComponent;
}
/**
* @inheritdoc
*/
getPluginName(plugin: AddonModAssignPlugin): string {
// Check if there's a translated string for the plugin.
const translationId = this.prefix + 'pluginname';
const translation = Translate.instant(translationId);
if (translationId != translation) {
// Translation found, use it.
return translation;
}
// Fallback to WS string.
return plugin.name;
}
}

View File

@ -0,0 +1,55 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Type } from '@angular/core';
import { AddonModAssignPlugin } from '@addons/mod/assign/services/assign';
import { AddonModAssignDefaultSubmissionHandler } from '@addons/mod/assign/services/handlers/default-submission';
import { Translate } from '@singletons';
import { CoreSitePluginsAssignSubmissionComponent } from '../../components/assign-submission/assign-submission';
/**
* Handler to display an assign submission site plugin.
*/
export class CoreSitePluginsAssignSubmissionHandler extends AddonModAssignDefaultSubmissionHandler {
constructor(public name: string, public type: string, protected prefix: string) {
super();
}
/**
* @inheritdoc
*/
getComponent(): Type<unknown> {
return CoreSitePluginsAssignSubmissionComponent;
}
/**
* @inheritdoc
*/
getPluginName(plugin: AddonModAssignPlugin): string {
// Check if there's a translated string for the plugin.
const translationId = this.prefix + 'pluginname';
const translation = Translate.instant(translationId);
if (translationId != translation) {
// Translation found, use it.
return translation;
}
// Fallback to WS string.
return plugin.name;
}
}

View File

@ -0,0 +1,31 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { CoreDelegateHandler } from '@classes/delegate';
/**
* Super class for handlers for site plugins.
*/
export class CoreSitePluginsBaseHandler implements CoreDelegateHandler {
constructor(public name: string) { }
/**
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return true;
}
}

View File

@ -0,0 +1,91 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Type } from '@angular/core';
import { CoreError } from '@classes/errors/error';
import { CoreBlockPreRenderedComponent } from '@features/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockDelegate, CoreBlockHandler, CoreBlockHandlerData } from '@features/block/services/block-delegate';
import { CoreCourseBlock } from '@features/course/services/course';
import { CoreSitePluginsBlockComponent } from '@features/siteplugins/components/block/block';
import { CoreSitePluginsOnlyTitleBlockComponent } from '@features/siteplugins/components/only-title-block/only-title-block';
import { CoreSitePluginsBlockHandlerData, CoreSitePluginsContent } from '@features/siteplugins/services/siteplugins';
import { CoreLogger } from '@singletons/logger';
import { CoreSitePluginsBaseHandler } from './base-handler';
/**
* Handler to support a block using a site plugin.
*/
export class CoreSitePluginsBlockHandler extends CoreSitePluginsBaseHandler implements CoreBlockHandler {
protected logger: CoreLogger;
constructor(
name: string,
public title: string,
public blockName: string,
protected handlerSchema: CoreSitePluginsBlockHandlerData,
protected initResult: CoreSitePluginsContent | null,
) {
super(name);
this.logger = CoreLogger.getInstance('CoreSitePluginsBlockHandler');
}
/**
* @inheritdoc
*/
async getDisplayData(
block: CoreCourseBlock,
contextLevel: string,
instanceId: number,
): Promise<CoreBlockHandlerData> {
const className = this.handlerSchema.displaydata?.class || 'block_' + block.name;
let component: Type<unknown> | undefined;
if (this.handlerSchema.displaydata?.type == 'title') {
component = CoreSitePluginsOnlyTitleBlockComponent;
} else if (this.handlerSchema.displaydata?.type == 'prerendered') {
component = CoreBlockPreRenderedComponent;
} else if (this.handlerSchema.fallback && !this.handlerSchema.method) {
// Try to use the fallback block.
const originalName = block.name;
block.name = this.handlerSchema.fallback;
try {
const displayData = await CoreBlockDelegate.getBlockDisplayData(block, contextLevel, instanceId);
if (!displayData) {
throw new CoreError('Cannot get display data for fallback block.');
}
this.logger.debug(`Using fallback "${this.handlerSchema.fallback}" for block "${originalName}"`);
component = displayData.component;
} catch (error) {
this.logger.error(`Error using fallback "${this.handlerSchema.fallback}" for block "${originalName}", ` +
'maybe it doesn\'t exist or isn\'t enabled.', error);
throw error;
}
} else {
component = CoreSitePluginsBlockComponent;
}
return {
title: this.title,
class: className,
component,
};
}
}

View File

@ -0,0 +1,61 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Type } from '@angular/core';
import { CoreCourseFormatHandler } from '@features/course/services/format-delegate';
import { CoreSitePluginsCourseFormatHandlerData } from '@features/siteplugins/services/siteplugins';
import { CoreSitePluginsBaseHandler } from './base-handler';
import { CoreSitePluginsCourseFormatComponent } from '../../components/course-format/course-format';
/**
* Handler to support a course format using a site plugin.
*/
export class CoreSitePluginsCourseFormatHandler extends CoreSitePluginsBaseHandler implements CoreCourseFormatHandler {
constructor(name: string, public format: string, protected handlerSchema: CoreSitePluginsCourseFormatHandlerData) {
super(name);
}
/**
* @inheritdoc
*/
canViewAllSections(): boolean {
return this.handlerSchema.canviewallsections ?? true;
}
/**
* @inheritdoc
*/
displayEnableDownload(): boolean {
return this.handlerSchema.displayenabledownload ?? true;
}
/**
* @inheritdoc
*/
displaySectionSelector(): boolean {
return this.handlerSchema.displaysectionselector ?? true;
}
/**
* @inheritdoc
*/
async getCourseFormatComponent(): Promise<Type<unknown> | undefined> {
if (this.handlerSchema.method) {
return CoreSitePluginsCourseFormatComponent;
}
}
}

View File

@ -0,0 +1,137 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 {
CoreCourseOptionsHandler,
CoreCourseOptionsHandlerData,
CoreCourseOptionsMenuHandlerData,
} from '@features/course/services/course-options-delegate';
import { CoreCourseAnyCourseDataWithOptions } from '@features/courses/services/courses';
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
import {
CoreSitePlugins,
CoreSitePluginsContent,
CoreSitePluginsCourseOptionHandlerData,
CoreSitePluginsPlugin,
} from '@features/siteplugins/services/siteplugins';
import { CoreUtils, PromiseDefer } from '@services/utils/utils';
import { CoreSitePluginsBaseHandler } from './base-handler';
/**
* Handler to display a site plugin in course options.
*/
export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandler implements CoreCourseOptionsHandler {
priority: number;
isMenuHandler: boolean;
protected updatingDefer?: PromiseDefer<void>;
constructor(
name: string,
protected title: string,
protected plugin: CoreSitePluginsPlugin,
protected handlerSchema: CoreSitePluginsCourseOptionHandlerData,
protected initResult: CoreSitePluginsContent | null,
) {
super(name);
this.priority = handlerSchema.priority || 0;
this.isMenuHandler = !!handlerSchema.ismenuhandler;
}
/**
* @inheritdoc
*/
async isEnabledForCourse(courseId: number): Promise<boolean> {
// Wait for "init" result to be updated.
if (this.updatingDefer) {
await this.updatingDefer.promise;
}
return CoreSitePlugins.isHandlerEnabledForCourse(
courseId,
this.handlerSchema.restricttoenrolledcourses,
this.initResult?.restrict,
);
}
/**
* @inheritdoc
*/
getDisplayData(): CoreCourseOptionsHandlerData {
return {
title: this.title,
class: this.handlerSchema.displaydata?.class,
page: '@todo CoreSitePluginsCourseOptionComponent',
pageParams: {
handlerUniqueName: this.name,
},
};
}
/**
* @inheritdoc
*/
getMenuDisplayData(course: CoreCourseAnyCourseDataWithOptions): CoreCourseOptionsMenuHandlerData {
return {
title: this.title,
class: this.handlerSchema.displaydata?.class,
icon: this.handlerSchema.displaydata?.icon || '',
page: '@todo CoreSitePluginsPluginPage',
pageParams: {
title: this.title,
component: this.plugin.component,
method: this.handlerSchema.method,
args: {
courseid: course.id,
},
initResult: this.initResult,
ptrEnabled: this.handlerSchema.ptrenabled,
},
};
}
/**
* @inheritdoc
*/
prefetch(course: CoreEnrolledCourseDataWithExtraInfoAndOptions): Promise<void> {
const args = {
courseid: course.id,
};
const component = this.plugin.component;
return CoreSitePlugins.prefetchFunctions(component, args, this.handlerSchema, course.id, undefined, true);
}
/**
* Set init result.
*
* @param result Result to set.
*/
setInitResult(result: CoreSitePluginsContent | null): void {
this.initResult = result;
this.updatingDefer?.resolve();
delete this.updatingDefer;
}
/**
* Mark init being updated.
*/
updatingInit(): void {
this.updatingDefer = CoreUtils.promiseDefer();
}
}

View File

@ -0,0 +1,62 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate';
import {
CoreSitePluginsContent,
CoreSitePluginsMainMenuHandlerData,
CoreSitePluginsPlugin,
} from '@features/siteplugins/services/siteplugins';
import { CoreSitePluginsBaseHandler } from './base-handler';
/**
* Handler to display a site plugin in the main menu.
*/
export class CoreSitePluginsMainMenuHandler extends CoreSitePluginsBaseHandler implements CoreMainMenuHandler {
priority: number;
constructor(
name: string,
protected title: string,
protected plugin: CoreSitePluginsPlugin,
protected handlerSchema: CoreSitePluginsMainMenuHandlerData,
protected initResult: CoreSitePluginsContent | null,
) {
super(name);
this.priority = handlerSchema.priority || 0;
}
/**
* @inheritdoc
*/
getDisplayData(): CoreMainMenuHandlerData {
return {
title: this.title,
icon: this.handlerSchema.displaydata?.icon || 'fas-question',
class: this.handlerSchema.displaydata?.class,
page: '@todo CoreSitePluginsPluginPage',
pageParams: {
title: this.title,
component: this.plugin.component,
method: this.handlerSchema.method,
initResult: this.initResult,
ptrEnabled: this.handlerSchema.ptrenabled,
},
onlyInMore: true,
};
}
}

View File

@ -0,0 +1,58 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { AddonMessageOutputHandler, AddonMessageOutputHandlerData } from '@addons/messageoutput/services/messageoutput-delegate';
import {
CoreSitePluginsContent,
CoreSitePluginsMessageOutputHandlerData,
CoreSitePluginsPlugin,
} from '@features/siteplugins/services/siteplugins';
import { CoreSitePluginsBaseHandler } from './base-handler';
/**
* Handler to display a message output settings option.
*/
export class CoreSitePluginsMessageOutputHandler extends CoreSitePluginsBaseHandler implements AddonMessageOutputHandler {
constructor(
name: string,
public processorName: string,
protected title: string,
protected plugin: CoreSitePluginsPlugin,
protected handlerSchema: CoreSitePluginsMessageOutputHandlerData,
protected initResult: CoreSitePluginsContent | null,
) {
super(name);
}
/**
* @inheritdoc
*/
getDisplayData(): AddonMessageOutputHandlerData {
return {
priority: this.handlerSchema.priority || 0,
label: this.title,
icon: this.handlerSchema.displaydata?.icon || 'fas-question',
page: '@todo CoreSitePluginsPluginPage',
pageParams: {
title: this.title,
component: this.plugin.component,
method: this.handlerSchema.method,
initResult: this.initResult,
ptrEnabled: this.handlerSchema.ptrenabled,
},
};
}
}

View File

@ -0,0 +1,166 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Type } from '@angular/core';
import { CoreCourseAnyModuleData, CoreCourseWSModule } from '@features/course/services/course';
import { CoreCourseModule } from '@features/course/services/course-helper';
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
import { CoreSitePluginsModuleIndexComponent } from '@features/siteplugins/components/module-index/module-index';
import {
CoreSitePlugins,
CoreSitePluginsContent,
CoreSitePluginsCourseModuleHandlerData,
CoreSitePluginsPlugin,
} from '@features/siteplugins/services/siteplugins';
import { CoreNavigationOptions } from '@services/navigator';
import { CoreLogger } from '@singletons/logger';
import { CoreSitePluginsBaseHandler } from './base-handler';
/**
* Handler to support a module using a site plugin.
*/
export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler implements CoreCourseModuleHandler {
supportedFeatures?: Record<string, unknown>;
supportsFeature?: (feature: string) => unknown;
protected logger: CoreLogger;
constructor(
name: string,
public modName: string,
protected plugin: CoreSitePluginsPlugin,
protected handlerSchema: CoreSitePluginsCourseModuleHandlerData,
protected initResult: CoreSitePluginsContent | null,
) {
super(name);
this.logger = CoreLogger.getInstance('CoreSitePluginsModuleHandler');
this.supportedFeatures = handlerSchema.supportedfeatures;
if (initResult?.jsResult && initResult.jsResult.supportsFeature) {
// The init result defines a function to check if a feature is supported, use it.
this.supportsFeature = initResult.jsResult.supportsFeature.bind(initResult.jsResult);
}
}
/**
* @inheritdoc
*/
getData(
module: CoreCourseAnyModuleData,
courseId: number,
sectionId?: number,
forCoursePage?: boolean,
): CoreCourseModuleHandlerData {
const callMethod = forCoursePage && this.handlerSchema.coursepagemethod;
if ('noviewlink' in module && module.noviewlink && !callMethod) {
// The module doesn't link to a new page (similar to label). Only display the description.
const title = module.description;
module.description = '';
return {
icon: this.getIconSrc(),
title: title || '',
a11yTitle: '',
class: this.handlerSchema.displaydata?.class,
};
}
const hasOffline = !!(this.handlerSchema.offlinefunctions && Object.keys(this.handlerSchema.offlinefunctions).length);
const showDowloadButton = this.handlerSchema.downloadbutton;
const handlerData: CoreCourseModuleHandlerData = {
title: module.name,
icon: this.getIconSrc(),
class: this.handlerSchema.displaydata?.class,
showDownloadButton: typeof showDowloadButton != 'undefined' ? showDowloadButton : hasOffline,
};
if (this.handlerSchema.method) {
// There is a method, add an action.
handlerData.action = (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions) => {
event.preventDefault();
event.stopPropagation();
// @todo navCtrl.push('CoreSitePluginsModuleIndexPage', {
// title: module.name,
// module: module,
// courseId: courseId
// }, options);
};
}
if (callMethod && module.visibleoncoursepage !== 0) {
// Call the method to get the course page template.
this.loadCoursePageTemplate(module, courseId, handlerData);
}
return handlerData;
}
/**
* Load and use template for course page.
*
* @param module Module.
* @param courseId Course ID.
* @param handlerData Handler data.
* @return Promise resolved when done.
*/
protected async loadCoursePageTemplate(
module: CoreCourseAnyModuleData,
courseId: number,
handlerData: CoreCourseModuleHandlerData,
): Promise<void> {
// Call the method to get the course page template.
handlerData.loading = true;
const args = {
courseid: courseId,
cmid: module.id,
};
try {
const result = await CoreSitePlugins.getContent(
this.plugin.component,
this.handlerSchema.coursepagemethod!,
args,
);
// Use the html returned.
handlerData.title = result.templates[0]?.html ?? '';
(<CoreCourseWSModule> module).description = '';
} catch (error) {
this.logger.error('Error calling course page method:', error);
} finally {
handlerData.loading = false;
}
}
/**
* @inheritdoc
*/
getIconSrc(): string | undefined {
return this.handlerSchema.displaydata?.icon;
}
/**
* @inheritdoc
*/
async getMainComponent(): Promise<Type<unknown>> {
return CoreSitePluginsModuleIndexComponent;
}
}

View File

@ -0,0 +1,236 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
import { CoreSitePlugins, CoreSitePluginsCourseModuleHandlerData } from '@features/siteplugins/services/siteplugins';
import { CoreFilepool } from '@services/filepool';
import { CoreFileSizeSum } from '@services/plugin-file-delegate';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
/**
* Handler to prefetch a module site plugin.
*/
export class CoreSitePluginsModulePrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {
protected isResource: boolean;
constructor(
component: string,
name: string,
modName: string,
protected handlerSchema: CoreSitePluginsCourseModuleHandlerData,
) {
super();
this.component = component;
this.name = name;
this.modName = modName;
this.isResource = !!handlerSchema.isresource;
if (handlerSchema.updatesnames) {
try {
this.updatesNames = new RegExp(handlerSchema.updatesnames);
} catch (ex) {
// Ignore errors.
}
}
}
/**
* @inheritdoc
*/
download(module: CoreCourseAnyModuleData, courseId: number, dirPath?: string): Promise<void> {
const siteId = CoreSites.getCurrentSiteId();
return this.prefetchPackage(
module,
courseId,
this.downloadPrefetchPlugin.bind(this, module, courseId, false, dirPath, siteId),
siteId,
);
}
/**
* Download or prefetch the plugin, downloading the files and calling the needed WS.
*
* @param module The module object returned by WS.
* @param courseId Course ID.
* @param prefetch True to prefetch, false to download right away.
* @param dirPath Path of the directory where to store all the content files.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when done.
*/
protected async downloadPrefetchPlugin(
module: CoreCourseAnyModuleData,
courseId: number,
prefetch: boolean,
dirPath?: string,
siteId?: string,
): Promise<void> {
const site = await CoreSites.getSite(siteId);
const args = {
courseid: courseId,
cmid: module.id,
userid: site.getUserId(),
};
await Promise.all([
// Download the files (if any).
this.downloadOrPrefetchFiles(site.getId(), module, courseId, prefetch, dirPath),
// Call all the offline functions.
CoreSitePlugins.prefetchFunctions(
this.component,
args,
this.handlerSchema,
courseId,
module,
prefetch,
dirPath,
site,
),
]);
}
/**
* Download or prefetch the plugin files.
*
* @param siteId Site ID.
* @param module The module object returned by WS.
* @param courseId Course ID.
* @param prefetch True to prefetch, false to download right away.
* @param dirPath Path of the directory where to store all the content files.
* @return Promise resolved when done.
*/
protected async downloadOrPrefetchFiles(
siteId: string,
module: CoreCourseAnyModuleData,
courseId: number,
prefetch: boolean,
dirPath?: string,
): Promise<void> {
// Load module contents (ignore cache so we always have the latest data).
await this.loadContents(module, courseId, true);
// Get the intro files.
const introFiles = await this.getIntroFiles(module, courseId);
const contentFiles = this.getContentDownloadableFiles(module);
if (dirPath) {
await Promise.all([
// Download intro files in filepool root folder.
CoreFilepool.downloadOrPrefetchFiles(siteId, introFiles, prefetch, false, this.component, module.id),
// Download content files inside dirPath.
CoreFilepool.downloadOrPrefetchFiles(
siteId,
contentFiles,
prefetch,
false,
this.component,
module.id,
dirPath,
),
]);
} else {
// No dirPath, download everything in filepool root folder.
await CoreFilepool.downloadOrPrefetchFiles(
siteId,
introFiles.concat(contentFiles),
prefetch,
false,
this.component,
module.id,
);
}
}
/**
* @inheritdoc
*/
async getDownloadSize(): Promise<CoreFileSizeSum> {
// In most cases, to calculate the size we'll have to do all the WS calls. Just return unknown size.
return { size: -1, total: false };
}
/**
* @inheritdoc
*/
async invalidateContent(moduleId: number, courseId: number): Promise<void> {
const currentSite = CoreSites.getCurrentSite();
if (!currentSite) {
return;
}
const promises: Promise<void>[] = [];
const siteId = currentSite.getId();
const args = {
courseid: courseId,
cmid: moduleId,
userid: currentSite.getUserId(),
};
// Invalidate files and the module.
promises.push(CoreFilepool.invalidateFilesByComponent(siteId, this.component, moduleId));
promises.push(CoreCourse.invalidateModule(moduleId, siteId));
// Also invalidate all the WS calls.
for (const method in this.handlerSchema.offlinefunctions) {
if (currentSite.wsAvailable(method)) {
// The method is a WS.
promises.push(currentSite.invalidateWsCacheForKey(CoreSitePlugins.getCallWSCacheKey(method, args)));
} else {
// It's a method to get content.
promises.push(CoreSitePlugins.invalidateContent(this.component, method, args, siteId));
}
}
return CoreUtils.allPromises(promises);
}
/**
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return true;
}
/**
* @inheritdoc
*/
async loadContents(module: CoreCourseAnyModuleData, courseId: number, ignoreCache?: boolean): Promise<void> {
if (this.isResource) {
return CoreCourse.loadModuleContents(module, courseId, undefined, false, ignoreCache);
}
}
/**
* @inheritdoc
*/
prefetch(module: CoreCourseAnyModuleData, courseId?: number, single?: boolean, dirPath?: string): Promise<void> {
const siteId = CoreSites.getCurrentSiteId();
return this.prefetchPackage(
module,
courseId,
this.downloadPrefetchPlugin.bind(this, module, courseId, true, dirPath, siteId),
siteId,
);
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Type } from '@angular/core';
import { CoreQuestionBehaviourBaseHandler } from '@features/question/classes/base-behaviour-handler';
import { CoreSitePluginsQuestionBehaviourComponent } from '@features/siteplugins/components/question-behaviour/question-behaviour';
/**
* Handler to display a question behaviour site plugin.
*/
export class CoreSitePluginsQuestionBehaviourHandler extends CoreQuestionBehaviourBaseHandler {
constructor(public name: string, public type: string, public hasTemplate: boolean) {
super();
}
/**
* @inheritdoc
*/
handleQuestion(): undefined | Type<unknown>[] {
if (this.hasTemplate) {
return [CoreSitePluginsQuestionBehaviourComponent];
}
}
}

View File

@ -0,0 +1,36 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Type } from '@angular/core';
import { CoreQuestionBaseHandler } from '@features/question/classes/base-question-handler';
import { CoreSitePluginsQuestionComponent } from '@features/siteplugins/components/question/question';
/**
* Handler to display a question site plugin.
*/
export class CoreSitePluginsQuestionHandler extends CoreQuestionBaseHandler {
constructor(public name: string, public type: string) {
super();
}
/**
* @inheritdoc
*/
getComponent(): Type<unknown> {
return CoreSitePluginsQuestionComponent;
}
}

View File

@ -0,0 +1,78 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Type } from '@angular/core';
import { AddonModQuizAccessRuleHandler } from '@addons/mod/quiz/services/access-rules-delegate';
import { CoreSitePluginsQuizAccessRuleComponent } from '../../components/quiz-access-rule/quiz-access-rule';
/**
* Handler to display a quiz access rule site plugin.
*/
export class CoreSitePluginsQuizAccessRuleHandler implements AddonModQuizAccessRuleHandler {
constructor(public name: string, public ruleName: string, public hasTemplate: boolean) { }
/**
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return true;
}
/**
* @inheritdoc
*/
isPreflightCheckRequired(): boolean {
return this.hasTemplate;
}
/**
* @inheritdoc
*/
getFixedPreflightData(): void {
// Nothing to do.
}
/**
* @inheritdoc
*/
getPreflightComponent(): undefined | Type<unknown> {
if (this.hasTemplate) {
return CoreSitePluginsQuizAccessRuleComponent;
}
}
/**
* @inheritdoc
*/
notifyPreflightCheckPassed(): void {
// Nothing to do.
}
/**
* @inheritdoc
*/
notifyPreflightCheckFailed(): void {
// Nothing to do.
}
/**
* @inheritdoc
*/
shouldShowTimeLeft(): boolean {
return false;
}
}

View File

@ -0,0 +1,63 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { CoreSettingsHandler, CoreSettingsHandlerData } from '@features/settings/services/settings-delegate';
import {
CoreSitePluginsContent,
CoreSitePluginsPlugin,
CoreSitePluginsSettingsHandlerData,
} from '@features/siteplugins/services/siteplugins';
import { CoreSitePluginsBaseHandler } from './base-handler';
/**
* Handler to display a site plugin in the settings.
*/
export class CoreSitePluginsSettingsHandler extends CoreSitePluginsBaseHandler implements CoreSettingsHandler {
priority: number;
constructor(
name: string,
protected title: string,
protected plugin: CoreSitePluginsPlugin,
protected handlerSchema: CoreSitePluginsSettingsHandlerData,
protected initResult: CoreSitePluginsContent | null,
) {
super(name);
this.priority = handlerSchema.priority || 0;
}
/**
* Returns the data needed to render the handler.
*
* @return Data.
*/
getDisplayData(): CoreSettingsHandlerData {
return {
title: this.title,
icon: this.handlerSchema.displaydata?.icon,
class: this.handlerSchema.displaydata?.class,
page: '@todo CoreSitePluginsPluginPage',
params: {
title: this.title,
component: this.plugin.component,
method: this.handlerSchema.method,
initResult: this.initResult,
ptrEnabled: this.handlerSchema.ptrenabled,
},
};
}
}

View File

@ -0,0 +1,127 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 {
CoreSitePlugins,
CoreSitePluginsContent,
CoreSitePluginsPlugin,
CoreSitePluginsUserHandlerData,
} from '@features/siteplugins/services/siteplugins';
import { CoreUserProfile } from '@features/user/services/user';
import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
import { CoreSites } from '@services/sites';
import { CoreUtils, PromiseDefer } from '@services/utils/utils';
import { CoreSitePluginsBaseHandler } from './base-handler';
/**
* Handler to display a site plugin in the user profile.
*/
export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandler implements CoreUserProfileHandler {
priority: number;
type: string;
protected updatingDefer?: PromiseDefer<void>;
constructor(
name: string,
protected title: string,
protected plugin: CoreSitePluginsPlugin,
protected handlerSchema: CoreSitePluginsUserHandlerData,
protected initResult: CoreSitePluginsContent | null,
) {
super(name);
this.priority = handlerSchema.priority || 0;
// Only support TYPE_COMMUNICATION and TYPE_NEW_PAGE.
this.type = handlerSchema.type != CoreUserDelegateService.TYPE_COMMUNICATION ?
CoreUserDelegateService.TYPE_NEW_PAGE : CoreUserDelegateService.TYPE_COMMUNICATION;
}
/**
* @inheritdoc
*/
async isEnabledForUser(
user: CoreUserProfile,
courseId?: number,
): Promise<boolean> {
// First check if it's enabled for the user.
const enabledForUser = CoreSitePlugins.isHandlerEnabledForUser(
user.id,
this.handlerSchema.restricttocurrentuser,
this.initResult?.restrict,
);
if (!enabledForUser) {
return false;
}
courseId = courseId || CoreSites.getCurrentSiteHomeId();
// Enabled for user, check if it's enabled for the course.
return CoreSitePlugins.isHandlerEnabledForCourse(
courseId,
this.handlerSchema.restricttoenrolledcourses,
this.initResult?.restrict,
);
}
/**
* @inheritdoc
*/
getDisplayData(): CoreUserProfileHandlerData {
return {
title: this.title,
icon: this.handlerSchema.displaydata?.icon,
class: this.handlerSchema.displaydata?.class,
action: (event: Event, user: CoreUserProfile, courseId?: number): void => {
event.preventDefault();
event.stopPropagation();
// @todo navCtrl.push('CoreSitePluginsPluginPage', {
// title: this.title,
// component: this.plugin.component,
// method: this.handlerSchema.method,
// args: {
// courseid: courseId,
// userid: user.id
// },
// initResult: this.initResult,
// ptrEnabled: this.handlerSchema.ptrenabled,
// });
},
};
}
/**
* Set init result.
*
* @param result Result to set.
*/
setInitResult(result: CoreSitePluginsContent | null): void {
this.initResult = result;
this.updatingDefer?.resolve();
delete this.updatingDefer;
}
/**
* Mark init being updated.
*/
updatingInit(): void {
this.updatingDefer = CoreUtils.promiseDefer();
}
}

View File

@ -0,0 +1,58 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// 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 { Type } from '@angular/core';
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
import { CoreSitePluginsUserProfileFieldComponent } from '@features/siteplugins/components/user-profile-field/user-profile-field';
import { CoreUserProfileField } from '@features/user/services/user';
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '@features/user/services/user-profile-field-delegate';
import { CoreSitePluginsBaseHandler } from './base-handler';
/**
* Handler to display a site plugin in the user profile.
*/
export class CoreSitePluginsUserProfileFieldHandler extends CoreSitePluginsBaseHandler implements CoreUserProfileFieldHandler {
constructor(name: string, public type: string) {
super(name);
}
/**
* @inheritdoc
*/
getComponent(): Type<unknown> {
return CoreSitePluginsUserProfileFieldComponent;
}
/**
* @inheritdoc
*/
async getData(
field: AuthEmailSignupProfileField | CoreUserProfileField,
signup: boolean,
registerAuth: string,
formValues: Record<string, unknown>,
): Promise<CoreUserProfileFieldHandlerData> {
// No getData function implemented, use a default behaviour.
const name = 'profile_field_' + field.shortname;
return {
type: 'type' in field ? field.type : field.datatype!,
name: name,
value: formValues[name],
};
}
}