MOBILE-3664 siteplugins: Implement services
parent
9cc7c73b31
commit
99b665915b
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,922 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { CoreConstants } from '@/core/constants';
|
||||||
|
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||||
|
import { CoreCourseAnyModuleData } from '@features/course/services/course';
|
||||||
|
import { CoreCourses } from '@features/courses/services/courses';
|
||||||
|
import { CoreApp } from '@services/app';
|
||||||
|
import { CoreFilepool } from '@services/filepool';
|
||||||
|
import { CoreLang } from '@services/lang';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
import { CoreUtils, PromiseDefer } from '@services/utils/utils';
|
||||||
|
import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { CoreEvents } from '@singletons/events';
|
||||||
|
import { CoreLogger } from '@singletons/logger';
|
||||||
|
|
||||||
|
const ROOT_CACHE_KEY = 'CoreSitePlugins:';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to provide functionalities regarding site plugins.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class CoreSitePluginsProvider {
|
||||||
|
|
||||||
|
static readonly COMPONENT = 'CoreSitePlugins';
|
||||||
|
|
||||||
|
protected logger: CoreLogger;
|
||||||
|
protected sitePlugins: {[name: string]: CoreSitePluginsHandler} = {}; // Site plugins registered.
|
||||||
|
protected sitePluginPromises: {[name: string]: Promise<void>} = {}; // Promises of loading plugins.
|
||||||
|
protected fetchPluginsDeferred: PromiseDefer<void>;
|
||||||
|
hasSitePluginsLoaded = false;
|
||||||
|
sitePluginsFinishedLoading = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.logger = CoreLogger.getInstance('CoreSitePluginsProvider');
|
||||||
|
|
||||||
|
const observer = CoreEvents.on(CoreEvents.SITE_PLUGINS_LOADED, () => {
|
||||||
|
this.sitePluginsFinishedLoading = true;
|
||||||
|
observer?.off();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize deferred at start and on logout.
|
||||||
|
this.fetchPluginsDeferred = CoreUtils.promiseDefer();
|
||||||
|
CoreEvents.on(CoreEvents.LOGOUT, () => {
|
||||||
|
this.fetchPluginsDeferred = CoreUtils.promiseDefer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add some params that will always be sent for get content.
|
||||||
|
*
|
||||||
|
* @param args Original params.
|
||||||
|
* @param site Site. If not defined, current site.
|
||||||
|
* @return Promise resolved with the new params.
|
||||||
|
*/
|
||||||
|
protected async addDefaultArgs<T extends Record<string, unknown> = Record<string, unknown>>(
|
||||||
|
args: T,
|
||||||
|
site?: CoreSite,
|
||||||
|
): Promise<T & CoreSitePluginsDefaultArgs> {
|
||||||
|
args = args || {};
|
||||||
|
site = site || CoreSites.getCurrentSite();
|
||||||
|
|
||||||
|
const lang = await CoreLang.getCurrentLanguage();
|
||||||
|
|
||||||
|
// Clone the object so the original one isn't modified.
|
||||||
|
// const argsToSend = CoreUtils.clone(args);
|
||||||
|
|
||||||
|
const defaultArgs: CoreSitePluginsDefaultArgs = {
|
||||||
|
userid: <number> args.userid ?? site?.getUserId(),
|
||||||
|
appid: CoreConstants.CONFIG.app_id,
|
||||||
|
appversioncode: CoreConstants.CONFIG.versioncode,
|
||||||
|
appversionname: CoreConstants.CONFIG.versionname,
|
||||||
|
applang: lang,
|
||||||
|
appcustomurlscheme: CoreConstants.CONFIG.customurlscheme,
|
||||||
|
appisdesktop: false,
|
||||||
|
appismobile: CoreApp.isMobile(),
|
||||||
|
appiswide: CoreApp.isWide(),
|
||||||
|
appplatform: 'browser',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (args.appismobile) {
|
||||||
|
defaultArgs.appplatform = CoreApp.isIOS() ? 'ios' : 'android';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...args,
|
||||||
|
...defaultArgs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call a WS for a site plugin.
|
||||||
|
*
|
||||||
|
* @param method WS method to use.
|
||||||
|
* @param data Data to send to the WS.
|
||||||
|
* @param preSets Extra options.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved with the response.
|
||||||
|
*/
|
||||||
|
async callWS<T = unknown>(
|
||||||
|
method: string,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
preSets?: CoreSiteWSPreSets,
|
||||||
|
siteId?: string,
|
||||||
|
): Promise<T> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
preSets = preSets || {};
|
||||||
|
preSets.cacheKey = preSets.cacheKey || this.getCallWSCacheKey(method, data);
|
||||||
|
|
||||||
|
return site.read<T>(method, data, preSets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the result of a init get_content and, optionally, the result of another get_content,
|
||||||
|
* build an object with the data to pass to the JS of the get_content.
|
||||||
|
*
|
||||||
|
* @param initResult Result of the init WS call.
|
||||||
|
* @param contentResult Result of the content WS call (if any).
|
||||||
|
* @return An object with the data to pass to the JS.
|
||||||
|
*/
|
||||||
|
createDataForJS(
|
||||||
|
initResult?: CoreSitePluginsContent | null,
|
||||||
|
contentResult?: CoreSitePluginsContent | null,
|
||||||
|
): Record<string, unknown> {
|
||||||
|
let data: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
if (initResult) {
|
||||||
|
// First of all, add the data returned by the init JS (if any).
|
||||||
|
data = Object.assign(data, initResult.jsResult || {});
|
||||||
|
|
||||||
|
// Now add some data returned by the init WS call.
|
||||||
|
data.INIT_TEMPLATES = CoreUtils.objectToKeyValueMap(initResult.templates, 'id', 'html');
|
||||||
|
data.INIT_OTHERDATA = initResult.otherdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentResult) {
|
||||||
|
// Now add the data returned by the content WS call.
|
||||||
|
data.CONTENT_TEMPLATES = CoreUtils.objectToKeyValueMap(contentResult.templates, 'id', 'html');
|
||||||
|
data.CONTENT_OTHERDATA = contentResult.otherdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for a WS call.
|
||||||
|
*
|
||||||
|
* @param method Name of the method.
|
||||||
|
* @param data Data to identify the WS call.
|
||||||
|
* @return Cache key.
|
||||||
|
*/
|
||||||
|
getCallWSCacheKey(method: string, data: Record<string, unknown>): string {
|
||||||
|
return this.getCallWSCommonCacheKey(method) + ':' + CoreUtils.sortAndStringify(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get common cache key for a WS call.
|
||||||
|
*
|
||||||
|
* @param method Name of the method.
|
||||||
|
* @return Cache key.
|
||||||
|
*/
|
||||||
|
protected getCallWSCommonCacheKey(method: string): string {
|
||||||
|
return ROOT_CACHE_KEY + 'ws:' + method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a certain content for a site plugin.
|
||||||
|
*
|
||||||
|
* @param component Component where the class is. E.g. mod_assign.
|
||||||
|
* @param method Method to execute in the class.
|
||||||
|
* @param args The params for the method.
|
||||||
|
* @param preSets Extra options.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved with the result.
|
||||||
|
*/
|
||||||
|
async getContent(
|
||||||
|
component: string,
|
||||||
|
method: string,
|
||||||
|
args?: Record<string, unknown>,
|
||||||
|
preSets?: CoreSiteWSPreSets,
|
||||||
|
siteId?: string,
|
||||||
|
): Promise<CoreSitePluginsContentParsed> {
|
||||||
|
this.logger.debug(`Get content for component '${component}' and method '${method}'`);
|
||||||
|
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
// Add some params that will always be sent.
|
||||||
|
args = args || {};
|
||||||
|
const argsToSend = await this.addDefaultArgs(args, site);
|
||||||
|
|
||||||
|
// Now call the WS.
|
||||||
|
const data: CoreSitePluginsGetContentWSParams = {
|
||||||
|
component: component,
|
||||||
|
method: method,
|
||||||
|
args: CoreUtils.objectToArrayOfObjects(argsToSend, 'name', 'value', true),
|
||||||
|
};
|
||||||
|
|
||||||
|
preSets = preSets || {};
|
||||||
|
preSets.cacheKey = this.getContentCacheKey(component, method, args);
|
||||||
|
preSets.updateFrequency = preSets.updateFrequency ?? CoreSite.FREQUENCY_OFTEN;
|
||||||
|
|
||||||
|
const result = await site.read<CoreSitePluginsGetContentWSResponse>('tool_mobile_get_content', data, preSets);
|
||||||
|
|
||||||
|
let otherData: Record<string, unknown> = {};
|
||||||
|
if (result.otherdata) {
|
||||||
|
otherData = <Record<string, unknown>> CoreUtils.objectToKeyValueMap(result.otherdata, 'name', 'value');
|
||||||
|
|
||||||
|
// Try to parse all properties that could be JSON encoded strings.
|
||||||
|
for (const name in otherData) {
|
||||||
|
const value = otherData[name];
|
||||||
|
|
||||||
|
if (typeof value == 'string' && (value[0] == '{' || value[0] == '[')) {
|
||||||
|
otherData[name] = CoreTextUtils.parseJSON(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign(result, { otherdata: otherData });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for get content WS calls.
|
||||||
|
*
|
||||||
|
* @param component Component where the class is. E.g. mod_assign.
|
||||||
|
* @param method Method to execute in the class.
|
||||||
|
* @param args The params for the method.
|
||||||
|
* @return Cache key.
|
||||||
|
*/
|
||||||
|
protected getContentCacheKey(component: string, method: string, args: Record<string, unknown>): string {
|
||||||
|
return ROOT_CACHE_KEY + 'content:' + component + ':' + method + ':' + CoreUtils.sortAndStringify(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of a WS param for prefetch.
|
||||||
|
*
|
||||||
|
* @param component The component of the handler.
|
||||||
|
* @param paramName Name of the param as defined by the handler.
|
||||||
|
* @param courseId Course ID (if prefetching a course).
|
||||||
|
* @param module The module object returned by WS (if prefetching a module).
|
||||||
|
* @return The value.
|
||||||
|
*/
|
||||||
|
protected getDownloadParam(
|
||||||
|
component: string,
|
||||||
|
paramName: string,
|
||||||
|
courseId?: number,
|
||||||
|
module?: CoreCourseAnyModuleData,
|
||||||
|
): [number] | number | undefined {
|
||||||
|
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 (plugin + handler).
|
||||||
|
*
|
||||||
|
* @param plugin Data of the plugin.
|
||||||
|
* @param handlerName Name of the handler inside the plugin.
|
||||||
|
* @return Unique name.
|
||||||
|
*/
|
||||||
|
getHandlerUniqueName(plugin: CoreSitePluginsPlugin, handlerName: string): string {
|
||||||
|
return plugin.addon + '_' + handlerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get site plugins for site.
|
||||||
|
*
|
||||||
|
* @param siteId Site ID.
|
||||||
|
* @return Promise resolved with the plugins.
|
||||||
|
*/
|
||||||
|
async getPlugins(siteId?: string): Promise<CoreSitePluginsPlugin[]> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
if (!CoreSitePlugins.isGetContentAvailable(site)) {
|
||||||
|
// Cannot load site plugins, so there's no point to fetch them.
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the list of plugins. Try not to use cache.
|
||||||
|
const data = await site.read<CoreSitePluginsGetPluginsSupportingMobileWSResponse>(
|
||||||
|
'tool_mobile_get_plugins_supporting_mobile',
|
||||||
|
{},
|
||||||
|
{ getFromCache: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return enabled plugins.
|
||||||
|
return data.plugins.filter((plugin) => this.isSitePluginEnabled(plugin, site));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a site plugin handler.
|
||||||
|
*
|
||||||
|
* @param name Unique name of the handler.
|
||||||
|
* @return Handler.
|
||||||
|
*/
|
||||||
|
getSitePluginHandler(name: string): CoreSitePluginsHandler | undefined {
|
||||||
|
return this.sitePlugins[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate all WS call to a certain method.
|
||||||
|
*
|
||||||
|
* @param method WS method to use.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
async invalidateAllCallWSForMethod(method: string, siteId?: string): Promise<void> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
await site.invalidateWsCacheForKeyStartingWith(this.getCallWSCommonCacheKey(method));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate a WS call.
|
||||||
|
*
|
||||||
|
* @param method WS method to use.
|
||||||
|
* @param data Data to send to the WS.
|
||||||
|
* @param preSets Extra options.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
async invalidateCallWS(
|
||||||
|
method: string,
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
preSets?: CoreSiteWSPreSets,
|
||||||
|
siteId?: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
preSets = preSets || {};
|
||||||
|
|
||||||
|
await site.invalidateWsCacheForKey(preSets.cacheKey || this.getCallWSCacheKey(method, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate a page content.
|
||||||
|
*
|
||||||
|
* @param component Component where the class is. E.g. mod_assign.
|
||||||
|
* @param method Method to execute in the class.
|
||||||
|
* @param args The params for the method.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
async invalidateContent(component: string, callback: string, args?: Record<string, unknown>, siteId?: string): Promise<void> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
await site.invalidateWsCacheForKey(this.getContentCacheKey(component, callback, args || {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the get content WS is available.
|
||||||
|
*
|
||||||
|
* @param site The site to check. If not defined, current site.
|
||||||
|
*/
|
||||||
|
isGetContentAvailable(site?: CoreSite): boolean {
|
||||||
|
site = site || CoreSites.getCurrentSite();
|
||||||
|
|
||||||
|
return !!site?.wsAvailable('tool_mobile_get_content');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a handler is enabled for a certain course.
|
||||||
|
*
|
||||||
|
* @param courseId Course ID to check.
|
||||||
|
* @param restrictEnrolled If true or undefined, handler is only enabled for courses the user is enrolled in.
|
||||||
|
* @param restrict Users and courses the handler is restricted to.
|
||||||
|
* @return Whether the handler is enabled.
|
||||||
|
*/
|
||||||
|
async isHandlerEnabledForCourse(
|
||||||
|
courseId: number,
|
||||||
|
restrictEnrolled?: boolean,
|
||||||
|
restrict?: CoreSitePluginsContentRestrict,
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (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.
|
||||||
|
try {
|
||||||
|
await CoreCourses.getUserCourse(courseId, true);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a handler is enabled for a certain user.
|
||||||
|
*
|
||||||
|
* @param userId User ID to check.
|
||||||
|
* @param restrictCurrent Whether handler is only enabled for current user.
|
||||||
|
* @param restrict Users and courses the handler is restricted to.
|
||||||
|
* @return Whether the handler is enabled.
|
||||||
|
*/
|
||||||
|
isHandlerEnabledForUser(userId: number, restrictCurrent?: boolean, restrict?: CoreSitePluginsContentRestrict): boolean {
|
||||||
|
if (restrictCurrent && userId != CoreSites.getCurrentSite()?.getUserId()) {
|
||||||
|
// Only enabled for current user.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restrict?.users?.indexOf(userId) == -1) {
|
||||||
|
// User is not in the list of restricted users.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a certain plugin is a site plugin and it's enabled in a certain site.
|
||||||
|
*
|
||||||
|
* @param plugin Data of the plugin.
|
||||||
|
* @param site Site affected.
|
||||||
|
* @return Whether it's a site plugin and it's enabled.
|
||||||
|
*/
|
||||||
|
isSitePluginEnabled(plugin: CoreSitePluginsPlugin, site: CoreSite): boolean {
|
||||||
|
if (site.isFeatureDisabled('sitePlugin_' + plugin.component + '_' + plugin.addon) || !plugin.handlers) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Site plugin not disabled. Check if it has handlers.
|
||||||
|
if (!plugin.parsedHandlers) {
|
||||||
|
plugin.parsedHandlers = CoreTextUtils.parseJSON(
|
||||||
|
plugin.handlers,
|
||||||
|
null,
|
||||||
|
this.logger.error.bind(this.logger, 'Error parsing site plugin handlers'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!(plugin.parsedHandlers && Object.keys(plugin.parsedHandlers).length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load other data into args as determined by useOtherData list.
|
||||||
|
* If useOtherData is undefined, it won't add any data.
|
||||||
|
* If useOtherData is an array, it will only copy the properties whose names are in the array.
|
||||||
|
* If useOtherData is any other value, it will copy all the data from otherData to args.
|
||||||
|
*
|
||||||
|
* @param args The current args.
|
||||||
|
* @param otherData All the other data.
|
||||||
|
* @param useOtherData Names of the attributes to include.
|
||||||
|
* @return New args.
|
||||||
|
*/
|
||||||
|
loadOtherDataInArgs(
|
||||||
|
args: Record<string, unknown> | undefined,
|
||||||
|
otherData?: Record<string, unknown>,
|
||||||
|
useOtherData?: string[] | unknown,
|
||||||
|
): Record<string, unknown> {
|
||||||
|
if (!args) {
|
||||||
|
args = {};
|
||||||
|
} else {
|
||||||
|
args = CoreUtils.clone(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
otherData = otherData || {};
|
||||||
|
|
||||||
|
if (typeof useOtherData == 'undefined') {
|
||||||
|
// No need to add other data, return args as they are.
|
||||||
|
return args;
|
||||||
|
} else if (Array.isArray(useOtherData)) {
|
||||||
|
// Include only the properties specified in the array.
|
||||||
|
for (const i in useOtherData) {
|
||||||
|
const name = useOtherData[i];
|
||||||
|
|
||||||
|
if (typeof otherData[name] == 'object' && otherData[name] !== null) {
|
||||||
|
// Stringify objects.
|
||||||
|
args[name] = JSON.stringify(otherData[name]);
|
||||||
|
} else {
|
||||||
|
args[name] = otherData[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Add all the data to args.
|
||||||
|
for (const name in otherData) {
|
||||||
|
if (typeof otherData[name] == 'object' && otherData[name] !== null) {
|
||||||
|
// Stringify objects.
|
||||||
|
args[name] = JSON.stringify(otherData[name]);
|
||||||
|
} else {
|
||||||
|
args[name] = otherData[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch offline functions for a site plugin handler.
|
||||||
|
*
|
||||||
|
* @param component The component of the handler.
|
||||||
|
* @param args Params to send to the get_content calls.
|
||||||
|
* @param handlerSchema The handler schema.
|
||||||
|
* @param courseId Course ID (if prefetching a course).
|
||||||
|
* @param module The module object returned by WS (if prefetching a module).
|
||||||
|
* @param prefetch True to prefetch, false to download right away.
|
||||||
|
* @param dirPath Path of the directory where to store all the content files.
|
||||||
|
* @param site Site. If not defined, current site.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async prefetchFunctions(
|
||||||
|
component: string,
|
||||||
|
args: Record<string, unknown>,
|
||||||
|
handlerSchema: CoreSitePluginsCourseModuleHandlerData,
|
||||||
|
courseId?: number,
|
||||||
|
module?: CoreCourseAnyModuleData,
|
||||||
|
prefetch?: boolean,
|
||||||
|
dirPath?: string,
|
||||||
|
site?: CoreSite,
|
||||||
|
): Promise<void> {
|
||||||
|
site = site || CoreSites.getCurrentSite();
|
||||||
|
if (!site || !handlerSchema.offlinefunctions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(Object.keys(handlerSchema.offlinefunctions).map(async(method) => {
|
||||||
|
if (site!.wsAvailable(method)) {
|
||||||
|
// The method is a WS.
|
||||||
|
const paramsList = handlerSchema.offlinefunctions![method];
|
||||||
|
const cacheKey = this.getCallWSCacheKey(method, args);
|
||||||
|
let params: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.callWS(method, params, { cacheKey });
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's a method to get content.
|
||||||
|
const preSets: CoreSiteWSPreSets = {
|
||||||
|
component: component,
|
||||||
|
};
|
||||||
|
if (module) {
|
||||||
|
preSets.componentId = module.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.getContent(component, method, args, preSets);
|
||||||
|
|
||||||
|
|
||||||
|
// Prefetch the files in the content.
|
||||||
|
if (result.files.length) {
|
||||||
|
await CoreFilepool.downloadOrPrefetchFiles(
|
||||||
|
site!.getId(),
|
||||||
|
result.files,
|
||||||
|
!!prefetch,
|
||||||
|
false,
|
||||||
|
component,
|
||||||
|
module?.id,
|
||||||
|
dirPath,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a site plugin handler.
|
||||||
|
*
|
||||||
|
* @param name A unique name to identify the handler.
|
||||||
|
* @param handler Handler to set.
|
||||||
|
*/
|
||||||
|
setSitePluginHandler(name: string, handler: CoreSitePluginsHandler): void {
|
||||||
|
this.sitePlugins[name] = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the promise for a plugin that is being initialised.
|
||||||
|
*
|
||||||
|
* @param component
|
||||||
|
* @param promise
|
||||||
|
*/
|
||||||
|
registerSitePluginPromise(component: string, promise: Promise<void>): void {
|
||||||
|
this.sitePluginPromises[component] = promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set plugins fetched.
|
||||||
|
*/
|
||||||
|
setPluginsFetched(): void {
|
||||||
|
this.fetchPluginsDeferred.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set plugins fetched.
|
||||||
|
*/
|
||||||
|
setPluginsLoaded(loaded?: boolean): void {
|
||||||
|
this.hasSitePluginsLoaded = !!loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is a plugin being initialised for the specified component?
|
||||||
|
*
|
||||||
|
* @param component
|
||||||
|
*/
|
||||||
|
sitePluginPromiseExists(component: string): boolean {
|
||||||
|
return !!this.sitePluginPromises[component];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the promise for a plugin that is being initialised.
|
||||||
|
*
|
||||||
|
* @param component
|
||||||
|
*/
|
||||||
|
sitePluginLoaded(component: string): Promise<void> | undefined {
|
||||||
|
return this.sitePluginPromises[component];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for fetch plugins to be done.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when site plugins have been fetched.
|
||||||
|
*/
|
||||||
|
waitFetchPlugins(): Promise<void> {
|
||||||
|
return this.fetchPluginsDeferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CoreSitePlugins = makeSingleton(CoreSitePluginsProvider, ['sitePluginsFinishedLoading', 'hasSitePluginsLoaded']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler of a site plugin.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsHandler = {
|
||||||
|
plugin: CoreSitePluginsPlugin; // Site plugin data.
|
||||||
|
handlerName: string; // Name of the handler.
|
||||||
|
handlerSchema: CoreSitePluginsHandlerData; // Handler's data.
|
||||||
|
initResult?: CoreSitePluginsContent | null; // Result of the init WS call (if any).
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default args added to site plugins calls.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsDefaultArgs = {
|
||||||
|
userid?: number;
|
||||||
|
appid: string;
|
||||||
|
appversioncode: number;
|
||||||
|
appversionname: string;
|
||||||
|
applang: string;
|
||||||
|
appcustomurlscheme: string;
|
||||||
|
appisdesktop: boolean;
|
||||||
|
appismobile: boolean;
|
||||||
|
appiswide: boolean;
|
||||||
|
appplatform: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params of tool_mobile_get_content WS.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsGetContentWSParams = {
|
||||||
|
component: string; // Component where the class is e.g. mod_assign.
|
||||||
|
method: string; // Method to execute in class \$component\output\mobile.
|
||||||
|
args?: { // Args for the method are optional.
|
||||||
|
name: string; // Param name.
|
||||||
|
value: string; // Param value.
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by tool_mobile_get_content WS.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsGetContentWSResponse = {
|
||||||
|
templates: CoreSitePluginsContentTemplate[]; // Templates required by the generated content.
|
||||||
|
javascript: string; // JavaScript code.
|
||||||
|
otherdata: { // Other data that can be used or manipulated by the template via 2-way data-binding.
|
||||||
|
name: string; // Field name.
|
||||||
|
value: string; // Field value.
|
||||||
|
}[];
|
||||||
|
files: CoreWSExternalFile[];
|
||||||
|
restrict: CoreSitePluginsContentRestrict; // Restrict this content to certain users or courses.
|
||||||
|
disabled?: boolean; // Whether we consider this disabled or not.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template data returned by tool_mobile_get_content WS.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsContentTemplate = {
|
||||||
|
id: string; // ID of the template.
|
||||||
|
html: string; // HTML code.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template data returned by tool_mobile_get_content WS.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsContentRestrict = {
|
||||||
|
users?: number[]; // List of allowed users.
|
||||||
|
courses?: number[]; // List of allowed courses.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by tool_mobile_get_content WS with calculated data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsContentParsed = Omit<CoreSitePluginsGetContentWSResponse, 'otherdata'> & {
|
||||||
|
otherdata: Record<string, unknown>; // Other data that can be used or manipulated by the template via 2-way data-binding.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by tool_mobile_get_content WS with calculated data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsContent = CoreSitePluginsContentParsed & {
|
||||||
|
disabled?: boolean;
|
||||||
|
jsResult?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by tool_mobile_get_plugins_supporting_mobile WS.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsGetPluginsSupportingMobileWSResponse = {
|
||||||
|
plugins: CoreSitePluginsWSPlugin[];
|
||||||
|
warnings?: CoreWSExternalWarning[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin data returned by tool_mobile_get_plugins_supporting_mobile WS.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsWSPlugin = {
|
||||||
|
component: string; // The plugin component name.
|
||||||
|
version: string; // The plugin version number.
|
||||||
|
addon: string; // The Mobile addon (package) name.
|
||||||
|
dependencies: string[]; // The list of Mobile addons this addon depends on.
|
||||||
|
fileurl: string; // The addon package url for download or empty if it doesn't exist.
|
||||||
|
filehash: string; // The addon package hash or empty if it doesn't exist.
|
||||||
|
filesize: number; // The addon package size or empty if it doesn't exist.
|
||||||
|
handlers?: string; // Handlers definition (JSON).
|
||||||
|
lang?: string; // Language strings used by the handlers (JSON).
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin data with some calculated data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsPlugin = CoreSitePluginsWSPlugin & {
|
||||||
|
parsedHandlers?: Record<string, CoreSitePluginsHandlerData> | null;
|
||||||
|
parsedLang?: Record<string, string[]> | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin handler data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsHandlerData = CoreSitePluginsInitHandlerData | CoreSitePluginsCourseOptionHandlerData |
|
||||||
|
CoreSitePluginsMainMenuHandlerData | CoreSitePluginsCourseModuleHandlerData | CoreSitePluginsCourseFormatHandlerData |
|
||||||
|
CoreSitePluginsUserHandlerData | CoreSitePluginsSettingsHandlerData | CoreSitePluginsMessageOutputHandlerData |
|
||||||
|
CoreSitePluginsBlockHandlerData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin handler data common to all delegates.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsHandlerCommonData = {
|
||||||
|
delegate?: string;
|
||||||
|
method?: string;
|
||||||
|
init?: string;
|
||||||
|
restricttocurrentuser?: boolean;
|
||||||
|
restricttoenrolledcourses?: boolean;
|
||||||
|
styles?: {
|
||||||
|
url?: string;
|
||||||
|
version?: number;
|
||||||
|
};
|
||||||
|
moodlecomponent?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Course option handler specific data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsCourseOptionHandlerData = CoreSitePluginsHandlerCommonData & {
|
||||||
|
displaydata?: {
|
||||||
|
title?: string;
|
||||||
|
class?: string;
|
||||||
|
icon?: string;
|
||||||
|
};
|
||||||
|
priority?: number;
|
||||||
|
ismenuhandler?: boolean;
|
||||||
|
ptrenabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main menu handler specific data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsMainMenuHandlerData = CoreSitePluginsHandlerCommonData & {
|
||||||
|
displaydata?: {
|
||||||
|
title?: string;
|
||||||
|
icon?: string;
|
||||||
|
class?: string;
|
||||||
|
};
|
||||||
|
priority?: number;
|
||||||
|
ptrenabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Course module handler specific data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsCourseModuleHandlerData = CoreSitePluginsHandlerCommonData & {
|
||||||
|
displaydata?: {
|
||||||
|
icon?: string;
|
||||||
|
class?: string;
|
||||||
|
};
|
||||||
|
method?: string;
|
||||||
|
offlinefunctions?: Record<string, string[]>;
|
||||||
|
downloadbutton?: boolean;
|
||||||
|
isresource?: boolean;
|
||||||
|
updatesnames?: string;
|
||||||
|
displayopeninbrowser?: boolean;
|
||||||
|
displaydescription?: boolean;
|
||||||
|
displayrefresh?: boolean;
|
||||||
|
displayprefetch?: boolean;
|
||||||
|
displaysize?: boolean;
|
||||||
|
coursepagemethod?: string;
|
||||||
|
ptrenabled?: boolean;
|
||||||
|
supportedfeatures?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Course format handler specific data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsCourseFormatHandlerData = CoreSitePluginsHandlerCommonData & {
|
||||||
|
canviewallsections?: boolean;
|
||||||
|
displayenabledownload?: boolean;
|
||||||
|
displaysectionselector?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User handler specific data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsUserHandlerData = CoreSitePluginsHandlerCommonData & {
|
||||||
|
displaydata?: {
|
||||||
|
title?: string;
|
||||||
|
icon?: string;
|
||||||
|
class?: string;
|
||||||
|
};
|
||||||
|
type?: string;
|
||||||
|
priority?: number;
|
||||||
|
ptrenabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings handler specific data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsSettingsHandlerData = CoreSitePluginsHandlerCommonData & {
|
||||||
|
displaydata?: {
|
||||||
|
title?: string;
|
||||||
|
icon?: string;
|
||||||
|
class?: string;
|
||||||
|
};
|
||||||
|
priority?: number;
|
||||||
|
ptrenabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message output handler specific data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsMessageOutputHandlerData = CoreSitePluginsHandlerCommonData & {
|
||||||
|
displaydata?: {
|
||||||
|
title?: string;
|
||||||
|
icon?: string;
|
||||||
|
};
|
||||||
|
priority?: number;
|
||||||
|
ptrenabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block handler specific data.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsBlockHandlerData = CoreSitePluginsHandlerCommonData & {
|
||||||
|
displaydata?: {
|
||||||
|
title?: string;
|
||||||
|
class?: string;
|
||||||
|
type?: string;
|
||||||
|
};
|
||||||
|
fallback?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common handler data with some data from the init method.
|
||||||
|
*/
|
||||||
|
export type CoreSitePluginsInitHandlerData = CoreSitePluginsHandlerCommonData & {
|
||||||
|
methodTemplates?: CoreSitePluginsContentTemplate[];
|
||||||
|
methodJSResult?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
methodOtherdata?: Record<string, unknown>;
|
||||||
|
};
|
Loading…
Reference in New Issue