1568 lines
57 KiB
TypeScript
1568 lines
57 KiB
TypeScript
// (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 { Params } from '@angular/router';
|
|
|
|
import { CoreApp } from '@services/app';
|
|
import { CoreEvents } from '@singletons/events';
|
|
import { CoreLogger } from '@singletons/logger';
|
|
import { CoreSitesCommonWSOptions, CoreSites } from '@services/sites';
|
|
import { CoreTimeUtils } from '@services/utils/time';
|
|
import { CoreUtils } from '@services/utils/utils';
|
|
import { CoreSiteWSPreSets, CoreSite } from '@classes/site';
|
|
import { CoreConstants } from '@/core/constants';
|
|
import { makeSingleton, Platform, Translate } from '@singletons';
|
|
import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
|
|
|
|
import { CoreCourseStatusDBRecord, COURSE_STATUS_TABLE } from './database/course';
|
|
import { CoreCourseOffline } from './course-offline';
|
|
import { CoreError } from '@classes/errors/error';
|
|
import {
|
|
CoreCourseAnyCourseData,
|
|
CoreCoursesProvider,
|
|
} from '../../courses/services/courses';
|
|
import { CoreDomUtils } from '@services/utils/dom';
|
|
import { CoreWSError } from '@classes/errors/wserror';
|
|
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
|
|
import { CoreCourseHelper, CoreCourseModuleCompletionData } from './course-helper';
|
|
import { CoreCourseFormatDelegate } from './format-delegate';
|
|
import { CoreCronDelegate } from '@services/cron';
|
|
import { CoreCourseLogCronHandler } from './handlers/log-cron';
|
|
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
|
|
import { CoreCourseAutoSyncData, CoreCourseSyncProvider } from './sync';
|
|
|
|
const ROOT_CACHE_KEY = 'mmCourse:';
|
|
|
|
declare module '@singletons/events' {
|
|
|
|
/**
|
|
* Augment CoreEventsData interface with events specific to this service.
|
|
*
|
|
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
|
|
*/
|
|
export interface CoreEventsData {
|
|
[CoreCourseSyncProvider.AUTO_SYNCED]: CoreCourseAutoSyncData;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Service that provides some features regarding a course.
|
|
*/
|
|
@Injectable({ providedIn: 'root' })
|
|
export class CoreCourseProvider {
|
|
|
|
static readonly ALL_SECTIONS_ID = -2;
|
|
static readonly STEALTH_MODULES_SECTION_ID = -1;
|
|
static readonly ACCESS_GUEST = 'courses_access_guest';
|
|
static readonly ACCESS_DEFAULT = 'courses_access_default';
|
|
static readonly ALL_COURSES_CLEARED = -1;
|
|
|
|
static readonly COMPLETION_TRACKING_NONE = 0;
|
|
static readonly COMPLETION_TRACKING_MANUAL = 1;
|
|
static readonly COMPLETION_TRACKING_AUTOMATIC = 2;
|
|
|
|
static readonly COMPLETION_INCOMPLETE = 0;
|
|
static readonly COMPLETION_COMPLETE = 1;
|
|
static readonly COMPLETION_COMPLETE_PASS = 2;
|
|
static readonly COMPLETION_COMPLETE_FAIL = 3;
|
|
|
|
static readonly COMPONENT = 'CoreCourse';
|
|
|
|
protected readonly CORE_MODULES = [
|
|
'assign', 'assignment', 'book', 'chat', 'choice', 'data', 'database', 'date', 'external-tool',
|
|
'feedback', 'file', 'folder', 'forum', 'glossary', 'ims', 'imscp', 'label', 'lesson', 'lti', 'page', 'quiz',
|
|
'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop', 'h5pactivity',
|
|
];
|
|
|
|
protected logger: CoreLogger;
|
|
|
|
constructor() {
|
|
this.logger = CoreLogger.getInstance('CoreCourseProvider');
|
|
}
|
|
|
|
/**
|
|
* Initialize.
|
|
*/
|
|
initialize(): void {
|
|
Platform.resume.subscribe(() => {
|
|
// Run the handler the app is open to keep user in online status.
|
|
setTimeout(() => {
|
|
CoreCronDelegate.forceCronHandlerExecution(CoreCourseLogCronHandler.name);
|
|
}, 1000);
|
|
});
|
|
|
|
CoreEvents.on(CoreEvents.LOGIN, () => {
|
|
setTimeout(() => {
|
|
// Ignore errors here, since probably login is not complete: it happens on token invalid.
|
|
CoreUtils.ignoreErrors(
|
|
CoreCronDelegate.forceCronHandlerExecution(CoreCourseLogCronHandler.name),
|
|
);
|
|
}, 1000);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if the get course blocks WS is available in current site.
|
|
*
|
|
* @param site Site to check. If not defined, current site.
|
|
* @return Whether it's available.
|
|
* @since 3.7
|
|
*/
|
|
canGetCourseBlocks(site?: CoreSite): boolean {
|
|
site = site || CoreSites.getCurrentSite();
|
|
|
|
return !!site && site.isVersionGreaterEqualThan('3.7') && site.wsAvailable('core_block_get_course_blocks');
|
|
}
|
|
|
|
/**
|
|
* Check whether the site supports requesting stealth modules.
|
|
*
|
|
* @param site Site. If not defined, current site.
|
|
* @return Whether the site supports requesting stealth modules.
|
|
* @since 3.4.6, 3.5.3, 3.6
|
|
*/
|
|
canRequestStealthModules(site?: CoreSite): boolean {
|
|
site = site || CoreSites.getCurrentSite();
|
|
|
|
return !!site && site.isVersionGreaterEqualThan(['3.4.6', '3.5.3']);
|
|
}
|
|
|
|
/**
|
|
* Check if module completion could have changed. If it could have, trigger event. This function must be used,
|
|
* for example, after calling a "module_view" WS since it can change the module completion.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param completion Completion status of the module.
|
|
*/
|
|
checkModuleCompletion(courseId: number, completion?: CoreCourseModuleCompletionData): void {
|
|
if (completion && completion.tracking === 2 && completion.state === 0) {
|
|
this.invalidateSections(courseId).finally(() => {
|
|
CoreEvents.trigger(CoreEvents.COMPLETION_MODULE_VIEWED, { courseId: courseId });
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear all courses status in a site.
|
|
*
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when all status are cleared.
|
|
*/
|
|
async clearAllCoursesStatus(siteId?: string): Promise<void> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
this.logger.debug('Clear all course status for site ' + site.id);
|
|
|
|
await site.getDb().deleteRecords(COURSE_STATUS_TABLE);
|
|
this.triggerCourseStatusChanged(CoreCourseProvider.ALL_COURSES_CLEARED, CoreConstants.NOT_DOWNLOADED, site.id);
|
|
}
|
|
|
|
/**
|
|
* Check if the current view is a certain course initial page.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @return Whether the current view is a certain course.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
currentViewIsCourse(courseId: number): boolean {
|
|
// @todo implement
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get completion status of all the activities in a course for a certain user.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @param userId User ID. If not defined, current user.
|
|
* @param forceCache True if it should return cached data. Has priority over ignoreCache.
|
|
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
|
|
* @param includeOffline True if it should load offline data in the completion status.
|
|
* @return Promise resolved with the completion statuses: object where the key is module ID.
|
|
*/
|
|
async getActivitiesCompletionStatus(
|
|
courseId: number,
|
|
siteId?: string,
|
|
userId?: number,
|
|
forceCache: boolean = false,
|
|
ignoreCache: boolean = false,
|
|
includeOffline: boolean = true,
|
|
): Promise<Record<string, CoreCourseCompletionActivityStatus>> {
|
|
|
|
const site = await CoreSites.getSite(siteId);
|
|
userId = userId || site.getUserId();
|
|
|
|
this.logger.debug(`Getting completion status for user ${userId} in course ${courseId}`);
|
|
|
|
const params: CoreCompletionGetActivitiesCompletionStatusWSParams = {
|
|
courseid: courseId,
|
|
userid: userId,
|
|
};
|
|
const preSets: CoreSiteWSPreSets = {
|
|
cacheKey: this.getActivitiesCompletionCacheKey(courseId, userId),
|
|
};
|
|
|
|
if (forceCache) {
|
|
preSets.omitExpires = true;
|
|
} else if (ignoreCache) {
|
|
preSets.getFromCache = false;
|
|
preSets.emergencyCache = false;
|
|
}
|
|
|
|
const data = await site.read<CoreCourseCompletionActivityStatusWSResponse>(
|
|
'core_completion_get_activities_completion_status',
|
|
params,
|
|
preSets,
|
|
);
|
|
|
|
if (!data || !data.statuses) {
|
|
throw Error('WS core_completion_get_activities_completion_status failed');
|
|
}
|
|
|
|
const completionStatus = CoreUtils.arrayToObject(data.statuses, 'cmid');
|
|
if (!includeOffline) {
|
|
return completionStatus;
|
|
}
|
|
|
|
try {
|
|
// Now get the offline completion (if any).
|
|
const offlineCompletions = await CoreCourseOffline.getCourseManualCompletions(courseId, site.id);
|
|
|
|
offlineCompletions.forEach((offlineCompletion) => {
|
|
|
|
if (offlineCompletion && typeof completionStatus[offlineCompletion.cmid] != 'undefined') {
|
|
const onlineCompletion = completionStatus[offlineCompletion.cmid];
|
|
|
|
// If the activity uses manual completion, override the value with the offline one.
|
|
if (onlineCompletion.tracking === 1) {
|
|
onlineCompletion.state = offlineCompletion.completed;
|
|
onlineCompletion.offline = true;
|
|
}
|
|
}
|
|
});
|
|
|
|
return completionStatus;
|
|
} catch {
|
|
// Ignore errors.
|
|
return completionStatus;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cache key for activities completion WS calls.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param userId User ID.
|
|
* @return Cache key.
|
|
*/
|
|
protected getActivitiesCompletionCacheKey(courseId: number, userId: number): string {
|
|
return ROOT_CACHE_KEY + 'activitiescompletion:' + courseId + ':' + userId;
|
|
}
|
|
|
|
/**
|
|
* Get course blocks.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the list of blocks.
|
|
* @since 3.7
|
|
*/
|
|
async getCourseBlocks(courseId: number, siteId?: string): Promise<CoreCourseBlock[]> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
const params: CoreBlockGetCourseBlocksWSParams = {
|
|
courseid: courseId,
|
|
returncontents: true,
|
|
};
|
|
const preSets: CoreSiteWSPreSets = {
|
|
cacheKey: this.getCourseBlocksCacheKey(courseId),
|
|
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
|
};
|
|
const result = await site.read<CoreCourseBlocksWSResponse>('core_block_get_course_blocks', params, preSets);
|
|
|
|
return result.blocks || [];
|
|
}
|
|
|
|
/**
|
|
* Get cache key for course blocks WS calls.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @return Cache key.
|
|
*/
|
|
protected getCourseBlocksCacheKey(courseId: number): string {
|
|
return ROOT_CACHE_KEY + 'courseblocks:' + courseId;
|
|
}
|
|
|
|
/**
|
|
* Get the data stored for a course.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the data.
|
|
*/
|
|
async getCourseStatusData(courseId: number, siteId?: string): Promise<CoreCourseStatusDBRecord> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
const entry: CoreCourseStatusDBRecord = await site.getDb().getRecord(COURSE_STATUS_TABLE, { id: courseId });
|
|
if (!entry) {
|
|
throw Error('No entry found on course status table');
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* Get a course status.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the status.
|
|
*/
|
|
async getCourseStatus(courseId: number, siteId?: string): Promise<string> {
|
|
try {
|
|
const entry = await this.getCourseStatusData(courseId, siteId);
|
|
|
|
return entry.status || CoreConstants.NOT_DOWNLOADED;
|
|
} catch {
|
|
return CoreConstants.NOT_DOWNLOADED;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtain ids of downloaded courses.
|
|
*
|
|
* @param siteId Site id.
|
|
* @return Resolves with an array containing downloaded course ids.
|
|
*/
|
|
async getDownloadedCourseIds(siteId?: string): Promise<number[]> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
const entries: CoreCourseStatusDBRecord[] = await site.getDb().getRecordsList(
|
|
COURSE_STATUS_TABLE,
|
|
'status',
|
|
[
|
|
CoreConstants.DOWNLOADED,
|
|
CoreConstants.DOWNLOADING,
|
|
CoreConstants.OUTDATED,
|
|
],
|
|
);
|
|
|
|
return entries.map((entry) => entry.id);
|
|
}
|
|
|
|
/**
|
|
* Get a module from Moodle.
|
|
*
|
|
* @param moduleId The module ID.
|
|
* @param courseId The course ID. Recommended to speed up the process and minimize data usage.
|
|
* @param sectionId The section ID.
|
|
* @param preferCache True if shouldn't call WS if data is cached, false otherwise.
|
|
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @param modName If set, the app will retrieve all modules of this type with a single WS call. This reduces the
|
|
* number of WS calls, but it isn't recommended for modules that can return a lot of contents.
|
|
* @return Promise resolved with the module.
|
|
*/
|
|
async getModule(
|
|
moduleId: number,
|
|
courseId?: number,
|
|
sectionId?: number,
|
|
preferCache: boolean = false,
|
|
ignoreCache: boolean = false,
|
|
siteId?: string,
|
|
modName?: string,
|
|
): Promise<CoreCourseWSModule> {
|
|
siteId = siteId || CoreSites.getCurrentSiteId();
|
|
|
|
// Helper function to do the WS request without processing the result.
|
|
const doRequest = async (
|
|
site: CoreSite,
|
|
moduleId: number,
|
|
modName: string | undefined,
|
|
includeStealth: boolean,
|
|
preferCache: boolean,
|
|
): Promise<CoreCourseWSSection[]> => {
|
|
const params: CoreCourseGetContentsParams = {
|
|
courseid: courseId!,
|
|
options: [],
|
|
};
|
|
const preSets: CoreSiteWSPreSets = {
|
|
omitExpires: preferCache,
|
|
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
|
};
|
|
|
|
if (includeStealth) {
|
|
params.options!.push({
|
|
name: 'includestealthmodules',
|
|
value: true,
|
|
});
|
|
}
|
|
|
|
// If modName is set, retrieve all modules of that type. Otherwise get only the module.
|
|
if (modName) {
|
|
params.options!.push({
|
|
name: 'modname',
|
|
value: modName,
|
|
});
|
|
preSets.cacheKey = this.getModuleByModNameCacheKey(modName);
|
|
} else {
|
|
params.options!.push({
|
|
name: 'cmid',
|
|
value: moduleId,
|
|
});
|
|
preSets.cacheKey = this.getModuleCacheKey(moduleId);
|
|
}
|
|
|
|
if (!preferCache && ignoreCache) {
|
|
preSets.getFromCache = false;
|
|
preSets.emergencyCache = false;
|
|
}
|
|
|
|
try {
|
|
const sections = await site.read<CoreCourseWSSection[]>('core_course_get_contents', params, preSets);
|
|
|
|
return sections;
|
|
} catch {
|
|
// The module might still be cached by a request with different parameters.
|
|
if (!ignoreCache && !CoreApp.isOnline()) {
|
|
if (includeStealth) {
|
|
// Older versions didn't include the includestealthmodules option.
|
|
return doRequest(site, moduleId, modName, false, true);
|
|
} else if (modName) {
|
|
// Falback to the request for the given moduleId only.
|
|
return doRequest(site, moduleId, undefined, this.canRequestStealthModules(site), true);
|
|
}
|
|
}
|
|
|
|
throw Error('WS core_course_get_contents failed, cache ignored');
|
|
}
|
|
};
|
|
|
|
if (!courseId) {
|
|
// No courseId passed, try to retrieve it.
|
|
const module = await this.getModuleBasicInfo(moduleId, siteId);
|
|
courseId = module.course;
|
|
}
|
|
|
|
let sections: CoreCourseWSSection[];
|
|
try {
|
|
const site = await CoreSites.getSite(siteId);
|
|
// We have courseId, we can use core_course_get_contents for compatibility.
|
|
this.logger.debug(`Getting module ${moduleId} in course ${courseId}`);
|
|
|
|
sections = await doRequest(site, moduleId, modName, this.canRequestStealthModules(site), preferCache);
|
|
} catch {
|
|
// Error getting the module. Try to get all contents (without filtering by module).
|
|
const preSets: CoreSiteWSPreSets = {
|
|
omitExpires: preferCache,
|
|
};
|
|
|
|
if (!preferCache && ignoreCache) {
|
|
preSets.getFromCache = false;
|
|
preSets.emergencyCache = false;
|
|
}
|
|
|
|
sections = await this.getSections(courseId, false, false, preSets, siteId);
|
|
}
|
|
|
|
let foundModule: CoreCourseWSModule | undefined;
|
|
|
|
const foundSection = sections.some((section) => {
|
|
if (sectionId != null &&
|
|
!isNaN(sectionId) &&
|
|
section.id != CoreCourseProvider.STEALTH_MODULES_SECTION_ID &&
|
|
sectionId != section.id
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
foundModule = section.modules.find((module) => module.id == moduleId);
|
|
|
|
return !!foundModule;
|
|
});
|
|
|
|
if (foundSection && foundModule) {
|
|
foundModule.course = courseId;
|
|
|
|
return foundModule;
|
|
}
|
|
|
|
throw Error('Module not found');
|
|
}
|
|
|
|
/**
|
|
* Gets a module basic info by module ID.
|
|
*
|
|
* @param moduleId Module ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the module's info.
|
|
*/
|
|
async getModuleBasicInfo(moduleId: number, siteId?: string): Promise<CoreCourseModuleBasicInfo> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
const params: CoreCourseGetCourseModuleWSParams = {
|
|
cmid: moduleId,
|
|
};
|
|
const preSets = {
|
|
cacheKey: this.getModuleCacheKey(moduleId),
|
|
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
|
};
|
|
const response = await site.read<CoreCourseGetCourseModuleWSResponse>('core_course_get_course_module', params, preSets);
|
|
|
|
if (response.warnings && response.warnings.length) {
|
|
throw new CoreWSError(response.warnings[0]);
|
|
} else if (response.cm) {
|
|
return response.cm;
|
|
}
|
|
|
|
throw Error('WS core_course_get_course_module failed.');
|
|
}
|
|
|
|
/**
|
|
* Gets a module basic grade info by module ID.
|
|
*
|
|
* If the user does not have permision to manage the activity false is returned.
|
|
*
|
|
* @param moduleId Module ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the module's grade info.
|
|
*/
|
|
async getModuleBasicGradeInfo(moduleId: number, siteId?: string): Promise<CoreCourseModuleGradeInfo | undefined> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
if (!site || !site.isVersionGreaterEqualThan('3.2')) {
|
|
// On 3.1 won't get grading info and will return undefined. See check bellow.
|
|
return;
|
|
}
|
|
|
|
const info = await this.getModuleBasicInfo(moduleId, siteId);
|
|
|
|
const grade: CoreCourseModuleGradeInfo = {
|
|
advancedgrading: info.advancedgrading,
|
|
grade: info.grade,
|
|
gradecat: info.gradecat,
|
|
gradepass: info.gradepass,
|
|
outcomes: info.outcomes,
|
|
scale: info.scale,
|
|
};
|
|
|
|
if (
|
|
typeof grade.grade != 'undefined' ||
|
|
typeof grade.advancedgrading != 'undefined' ||
|
|
typeof grade.outcomes != 'undefined'
|
|
) {
|
|
// On 3.1 won't get grading info and will return undefined.
|
|
return grade;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Gets a module basic info by instance.
|
|
*
|
|
* @param id Instance ID.
|
|
* @param module Name of the module. E.g. 'glossary'.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the module's info.
|
|
*/
|
|
async getModuleBasicInfoByInstance(id: number, module: string, siteId?: string): Promise<CoreCourseModuleBasicInfo> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
const params: CoreCourseGetCourseModuleByInstanceWSParams = {
|
|
instance: id,
|
|
module: module,
|
|
};
|
|
const preSets = {
|
|
cacheKey: this.getModuleBasicInfoByInstanceCacheKey(id, module),
|
|
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
|
};
|
|
const response: CoreCourseGetCourseModuleWSResponse =
|
|
await site.read('core_course_get_course_module_by_instance', params, preSets);
|
|
|
|
if (response.warnings && response.warnings.length) {
|
|
throw new CoreWSError(response.warnings[0]);
|
|
} else if (response.cm) {
|
|
return response.cm;
|
|
}
|
|
|
|
throw Error('WS core_course_get_course_module_by_instance failed');
|
|
}
|
|
|
|
/**
|
|
* Get cache key for get module by instance WS calls.
|
|
*
|
|
* @param id Instance ID.
|
|
* @param module Name of the module. E.g. 'glossary'.
|
|
* @return Cache key.
|
|
*/
|
|
protected getModuleBasicInfoByInstanceCacheKey(id: number, module: string): string {
|
|
return ROOT_CACHE_KEY + 'moduleByInstance:' + module + ':' + id;
|
|
}
|
|
|
|
/**
|
|
* Get cache key for module WS calls.
|
|
*
|
|
* @param moduleId Module ID.
|
|
* @return Cache key.
|
|
*/
|
|
protected getModuleCacheKey(moduleId: number): string {
|
|
return ROOT_CACHE_KEY + 'module:' + moduleId;
|
|
}
|
|
|
|
/**
|
|
* Get cache key for module by modname WS calls.
|
|
*
|
|
* @param modName Name of the module.
|
|
* @return Cache key.
|
|
*/
|
|
protected getModuleByModNameCacheKey(modName: string): string {
|
|
return ROOT_CACHE_KEY + 'module:modName:' + modName;
|
|
}
|
|
|
|
/**
|
|
* Returns the source to a module icon.
|
|
*
|
|
* @param moduleName The module name.
|
|
* @param modicon The mod icon string to use in case we are not using a core activity.
|
|
* @return The IMG src.
|
|
*/
|
|
getModuleIconSrc(moduleName: string, modicon?: string): string {
|
|
// @TODO: Check modicon url theme to apply other theme icons.
|
|
|
|
// Use default icon on core themes.
|
|
if (this.CORE_MODULES.indexOf(moduleName) < 0) {
|
|
if (modicon) {
|
|
return modicon;
|
|
}
|
|
|
|
moduleName = 'external-tool';
|
|
}
|
|
|
|
return 'assets/img/mod/' + moduleName + '.svg';
|
|
}
|
|
|
|
/**
|
|
* Get the section ID a module belongs to.
|
|
*
|
|
* @param moduleId The module ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the section ID.
|
|
*/
|
|
async getModuleSectionId(moduleId: number, siteId?: string): Promise<number> {
|
|
// Try to get the section using getModuleBasicInfo.
|
|
const module = await this.getModuleBasicInfo(moduleId, siteId);
|
|
|
|
return module.section;
|
|
}
|
|
|
|
/**
|
|
* Return a specific section.
|
|
*
|
|
* @param courseId The course ID.
|
|
* @param sectionId The section ID.
|
|
* @param excludeModules Do not return modules, return only the sections structure.
|
|
* @param excludeContents Do not return module contents (i.e: files inside a resource).
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the section.
|
|
*/
|
|
async getSection(
|
|
courseId: number,
|
|
sectionId: number,
|
|
excludeModules?: boolean,
|
|
excludeContents?: boolean,
|
|
siteId?: string,
|
|
): Promise<CoreCourseWSSection> {
|
|
|
|
if (sectionId < 0) {
|
|
throw new CoreError('Invalid section ID');
|
|
}
|
|
|
|
const sections = await this.getSections(courseId, excludeModules, excludeContents, undefined, siteId);
|
|
const section = sections.find((section) => section.id == sectionId);
|
|
|
|
if (section) {
|
|
return section;
|
|
}
|
|
|
|
throw new CoreError('Unknown section');
|
|
}
|
|
|
|
/**
|
|
* Get the course sections.
|
|
*
|
|
* @param courseId The course ID.
|
|
* @param excludeModules Do not return modules, return only the sections structure.
|
|
* @param excludeContents Do not return module contents (i.e: files inside a resource).
|
|
* @param preSets Presets to use.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @param includeStealthModules Whether to include stealth modules. Defaults to true.
|
|
* @return The reject contains the error message, else contains the sections.
|
|
*/
|
|
async getSections(
|
|
courseId: number,
|
|
excludeModules: boolean = false,
|
|
excludeContents: boolean = false,
|
|
preSets?: CoreSiteWSPreSets,
|
|
siteId?: string,
|
|
includeStealthModules: boolean = true,
|
|
): Promise<CoreCourseWSSection[]> {
|
|
|
|
const site = await CoreSites.getSite(siteId);
|
|
preSets = preSets || {};
|
|
preSets.cacheKey = this.getSectionsCacheKey(courseId);
|
|
preSets.updateFrequency = preSets.updateFrequency || CoreSite.FREQUENCY_RARELY;
|
|
|
|
const params: CoreCourseGetContentsParams = {
|
|
courseid: courseId,
|
|
options: [
|
|
{
|
|
name: 'excludemodules',
|
|
value: excludeModules,
|
|
},
|
|
{
|
|
name: 'excludecontents',
|
|
value: excludeContents,
|
|
},
|
|
],
|
|
};
|
|
if (this.canRequestStealthModules(site)) {
|
|
params.options!.push({
|
|
name: 'includestealthmodules',
|
|
value: includeStealthModules,
|
|
});
|
|
}
|
|
|
|
let sections: CoreCourseWSSection[];
|
|
try {
|
|
sections = await site.read('core_course_get_contents', params, preSets);
|
|
} catch {
|
|
// Error getting the data, it could fail because we added a new parameter and the call isn't cached.
|
|
// Retry without the new parameter and forcing cache.
|
|
preSets.omitExpires = true;
|
|
params.options!.splice(-1, 1);
|
|
sections = await site.read('core_course_get_contents', params, preSets);
|
|
}
|
|
|
|
const siteHomeId = site.getSiteHomeId();
|
|
let showSections = true;
|
|
if (courseId == siteHomeId) {
|
|
const storedNumSections = site.getStoredConfig('numsections');
|
|
showSections = typeof storedNumSections != 'undefined' && !!storedNumSections;
|
|
}
|
|
|
|
if (typeof showSections != 'undefined' && !showSections && sections.length > 0) {
|
|
// Get only the last section (Main menu block section).
|
|
sections.pop();
|
|
}
|
|
|
|
return sections;
|
|
}
|
|
|
|
/**
|
|
* Get cache key for section WS call.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @return Cache key.
|
|
*/
|
|
protected getSectionsCacheKey(courseId: number): string {
|
|
return ROOT_CACHE_KEY + 'sections:' + courseId;
|
|
}
|
|
|
|
/**
|
|
* Given a list of sections, returns the list of modules in the sections.
|
|
*
|
|
* @param sections Sections.
|
|
* @return Modules.
|
|
*/
|
|
getSectionsModules(sections: CoreCourseWSSection[]): CoreCourseWSModule[] {
|
|
if (!sections || !sections.length) {
|
|
return [];
|
|
}
|
|
|
|
return sections.reduce((previous: CoreCourseWSModule[], section) => previous.concat(section.modules || []), []);
|
|
}
|
|
|
|
/**
|
|
* Invalidates course blocks WS call.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when the data is invalidated.
|
|
*/
|
|
async invalidateCourseBlocks(courseId: number, siteId?: string): Promise<void> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
await site.invalidateWsCacheForKey(this.getCourseBlocksCacheKey(courseId));
|
|
}
|
|
|
|
/**
|
|
* Invalidates module WS call.
|
|
*
|
|
* @param moduleId Module ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @param modName Module name. E.g. 'label', 'url', ...
|
|
* @return Promise resolved when the data is invalidated.
|
|
*/
|
|
async invalidateModule(moduleId: number, siteId?: string, modName?: string): Promise<void> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
const promises: Promise<void>[] = [];
|
|
if (modName) {
|
|
promises.push(site.invalidateWsCacheForKey(this.getModuleByModNameCacheKey(modName)));
|
|
}
|
|
promises.push(site.invalidateWsCacheForKey(this.getModuleCacheKey(moduleId)));
|
|
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* Invalidates module WS call.
|
|
*
|
|
* @param id Instance ID.
|
|
* @param module Name of the module. E.g. 'glossary'.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when the data is invalidated.
|
|
*/
|
|
async invalidateModuleByInstance(id: number, module: string, siteId?: string): Promise<void> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
await site.invalidateWsCacheForKey(this.getModuleBasicInfoByInstanceCacheKey(id, module));
|
|
}
|
|
|
|
/**
|
|
* Invalidates sections WS call.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @param userId User ID. If not defined, current user.
|
|
* @return Promise resolved when the data is invalidated.
|
|
*/
|
|
async invalidateSections(courseId: number, siteId?: string, userId?: number): Promise<void> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
const promises: Promise<void>[] = [];
|
|
const siteHomeId = site.getSiteHomeId();
|
|
userId = userId || site.getUserId();
|
|
promises.push(site.invalidateWsCacheForKey(this.getSectionsCacheKey(courseId)));
|
|
promises.push(site.invalidateWsCacheForKey(this.getActivitiesCompletionCacheKey(courseId, userId)));
|
|
if (courseId == siteHomeId) {
|
|
promises.push(site.invalidateConfig());
|
|
}
|
|
|
|
await Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* Load module contents into module.contents if they aren't loaded already.
|
|
*
|
|
* @param module Module to load the contents.
|
|
* @param courseId The course ID. Recommended to speed up the process and minimize data usage.
|
|
* @param sectionId The section ID.
|
|
* @param preferCache True if shouldn't call WS if data is cached, false otherwise.
|
|
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @param modName If set, the app will retrieve all modules of this type with a single WS call. This reduces the
|
|
* number of WS calls, but it isn't recommended for modules that can return a lot of contents.
|
|
* @return Promise resolved when loaded.
|
|
*/
|
|
async loadModuleContents(
|
|
module: CoreCourseAnyModuleData,
|
|
courseId?: number,
|
|
sectionId?: number,
|
|
preferCache?: boolean,
|
|
ignoreCache?: boolean,
|
|
siteId?: string,
|
|
modName?: string,
|
|
): Promise<void> {
|
|
|
|
if (!ignoreCache && module.contents && module.contents.length) {
|
|
// Already loaded.
|
|
return;
|
|
}
|
|
|
|
const mod = await this.getModule(module.id, courseId, sectionId, preferCache, ignoreCache, siteId, modName);
|
|
module.contents = mod.contents;
|
|
}
|
|
|
|
/**
|
|
* Report a course and section as being viewed.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param sectionNumber Section number.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @param name Name of the course.
|
|
* @return Promise resolved when the WS call is successful.
|
|
*/
|
|
async logView(courseId: number, sectionNumber?: number, siteId?: string, name?: string): Promise<void> {
|
|
const params: CoreCourseViewCourseWSParams = {
|
|
courseid: courseId,
|
|
};
|
|
const wsName = 'core_course_view_course';
|
|
|
|
if (typeof sectionNumber != 'undefined') {
|
|
params.sectionnumber = sectionNumber;
|
|
}
|
|
|
|
const site = await CoreSites.getSite(siteId);
|
|
CorePushNotifications.logViewEvent(courseId, name, 'course', wsName, { sectionnumber: sectionNumber }, siteId);
|
|
const response: CoreStatusWithWarningsWSResponse = await site.write(wsName, params);
|
|
|
|
if (!response.status) {
|
|
throw Error('WS core_course_view_course failed.');
|
|
} else {
|
|
CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {
|
|
courseId: courseId,
|
|
action: CoreCoursesProvider.ACTION_VIEW,
|
|
}, site.getId());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Offline version for manually marking a module as completed.
|
|
*
|
|
* @param cmId The module ID.
|
|
* @param completed Whether the module is completed or not.
|
|
* @param courseId Course ID the module belongs to.
|
|
* @param courseName Course name. Recommended, it is used to display a better warning message.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when completion is successfully sent or stored.
|
|
*/
|
|
async markCompletedManually(
|
|
cmId: number,
|
|
completed: boolean,
|
|
courseId: number,
|
|
courseName?: string,
|
|
siteId?: string,
|
|
): Promise<CoreStatusWithWarningsWSResponse> {
|
|
|
|
siteId = siteId || CoreSites.getCurrentSiteId();
|
|
|
|
// Convenience function to store a completion to be synchronized later.
|
|
const storeOffline = (): Promise<CoreStatusWithWarningsWSResponse> =>
|
|
CoreCourseOffline.markCompletedManually(cmId, completed, courseId, courseName, siteId);
|
|
|
|
// The offline function requires a courseId and it could be missing because it's a calculated field.
|
|
if (!CoreApp.isOnline() && courseId) {
|
|
// App is offline, store the action.
|
|
return storeOffline();
|
|
}
|
|
|
|
// Try to send it to server.
|
|
try {
|
|
const result = await this.markCompletedManuallyOnline(cmId, completed, siteId);
|
|
|
|
// Data sent to server, if there is some offline data delete it now.
|
|
try {
|
|
await CoreCourseOffline.deleteManualCompletion(cmId, siteId);
|
|
} catch {
|
|
// Ignore errors, shouldn't happen.
|
|
}
|
|
|
|
return result;
|
|
} catch (error) {
|
|
if (CoreUtils.isWebServiceError(error) || !courseId) {
|
|
// The WebService has thrown an error, this means that responses cannot be submitted.
|
|
throw error;
|
|
} else {
|
|
// Couldn't connect to server, store it offline.
|
|
return storeOffline();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Offline version for manually marking a module as completed.
|
|
*
|
|
* @param cmId The module ID.
|
|
* @param completed Whether the module is completed or not.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when completion is successfully sent.
|
|
*/
|
|
async markCompletedManuallyOnline(
|
|
cmId: number,
|
|
completed: boolean,
|
|
siteId?: string,
|
|
): Promise<CoreStatusWithWarningsWSResponse> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
const params: CoreCompletionUpdateActivityCompletionStatusManuallyWSParams = {
|
|
cmid: cmId,
|
|
completed: completed,
|
|
};
|
|
|
|
const result = await site.write<CoreStatusWithWarningsWSResponse>(
|
|
'core_completion_update_activity_completion_status_manually',
|
|
params,
|
|
);
|
|
|
|
if (!result.status) {
|
|
if (result.warnings && result.warnings.length) {
|
|
throw new CoreWSError(result.warnings[0]);
|
|
} else {
|
|
throw new CoreError('Cannot change completion.');
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Check if a module has a view page. E.g. labels don't have a view page.
|
|
*
|
|
* @param module The module object.
|
|
* @return Whether the module has a view page.
|
|
*/
|
|
moduleHasView(module: CoreCourseModuleSummary | CoreCourseWSModule): boolean {
|
|
return !!module.url;
|
|
}
|
|
|
|
/**
|
|
* Wait for any course format plugin to load, and open the course page.
|
|
*
|
|
* If the plugin's promise is resolved, the course page will be opened. If it is rejected, they will see an error.
|
|
* If the promise for the plugin is still in progress when the user tries to open the course, a loader
|
|
* will be displayed until it is complete, before the course page is opened. If the promise is already complete,
|
|
* they will see the result immediately.
|
|
*
|
|
* This function must be in here instead of course helper to prevent circular dependencies.
|
|
*
|
|
* @param course Course to open
|
|
* @param params Other params to pass to the course page.
|
|
* @return Promise resolved when done.
|
|
*/
|
|
async openCourse(course: CoreCourseAnyCourseData | { id: number }, params?: Params): Promise<void> {
|
|
const loading = await CoreDomUtils.showModalLoading();
|
|
|
|
// Wait for site plugins to be fetched.
|
|
await CoreSitePlugins.waitFetchPlugins();
|
|
|
|
if (!('format' in course) || typeof course.format == 'undefined') {
|
|
const result = await CoreCourseHelper.getCourse(course.id);
|
|
|
|
course = result.course;
|
|
}
|
|
|
|
const format = 'format' in course && `format_${course.format}`;
|
|
|
|
if (!format || !CoreSitePlugins.sitePluginPromiseExists(`format_${format}`)) {
|
|
// No custom format plugin. We don't need to wait for anything.
|
|
await CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, params);
|
|
loading.dismiss();
|
|
|
|
return;
|
|
}
|
|
|
|
// This course uses a custom format plugin, wait for the format plugin to finish loading.
|
|
try {
|
|
await CoreSitePlugins.sitePluginLoaded(format);
|
|
|
|
// The format loaded successfully, but the handlers wont be registered until all site plugins have loaded.
|
|
if (CoreSitePlugins.sitePluginsFinishedLoading) {
|
|
return CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, params);
|
|
}
|
|
|
|
// Wait for plugins to be loaded.
|
|
const deferred = CoreUtils.promiseDefer<void>();
|
|
|
|
const observer = CoreEvents.on(CoreEvents.SITE_PLUGINS_LOADED, () => {
|
|
observer?.off();
|
|
|
|
CoreCourseFormatDelegate.openCourse(<CoreCourseAnyCourseData> course, params)
|
|
.then(deferred.resolve).catch(deferred.reject);
|
|
});
|
|
|
|
return deferred.promise;
|
|
} catch (error) {
|
|
// The site plugin failed to load. The user needs to restart the app to try loading it again.
|
|
const message = Translate.instant('core.courses.errorloadplugins');
|
|
const reload = Translate.instant('core.courses.reload');
|
|
const ignore = Translate.instant('core.courses.ignore');
|
|
|
|
await CoreDomUtils.showConfirm(message, '', reload, ignore);
|
|
window.location.reload();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Select a certain tab in the course. Please use currentViewIsCourse() first to verify user is viewing the course.
|
|
*
|
|
* @param name Name of the tab. If not provided, course contents.
|
|
* @param params Other params.
|
|
*/
|
|
selectCourseTab(name?: string, params?: Params): void {
|
|
params = params || {};
|
|
params.name = name || '';
|
|
|
|
CoreEvents.trigger(CoreEvents.SELECT_COURSE_TAB, params);
|
|
}
|
|
|
|
/**
|
|
* Change the course status, setting it to the previous status.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when the status is changed. Resolve param: new status.
|
|
*/
|
|
async setCoursePreviousStatus(courseId: number, siteId?: string): Promise<string> {
|
|
siteId = siteId || CoreSites.getCurrentSiteId();
|
|
|
|
this.logger.debug(`Set previous status for course ${courseId} in site ${siteId}`);
|
|
|
|
const site = await CoreSites.getSite(siteId);
|
|
const db = site.getDb();
|
|
const entry = await this.getCourseStatusData(courseId, siteId);
|
|
|
|
this.logger.debug(`Set previous status '${entry.status}' for course ${courseId}`);
|
|
|
|
const newData = {
|
|
id: courseId,
|
|
status: entry.previous || CoreConstants.NOT_DOWNLOADED,
|
|
updated: Date.now(),
|
|
// Going back from downloading to previous status, restore previous download time.
|
|
downloadTime: entry.status == CoreConstants.DOWNLOADING ? entry.previousDownloadTime : entry.downloadTime,
|
|
};
|
|
|
|
await db.updateRecords(COURSE_STATUS_TABLE, newData, { id: courseId });
|
|
// Success updating, trigger event.
|
|
this.triggerCourseStatusChanged(courseId, newData.status, siteId);
|
|
|
|
return newData.status;
|
|
}
|
|
|
|
/**
|
|
* Store course status.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param status New course status.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when the status is stored.
|
|
*/
|
|
async setCourseStatus(courseId: number, status: string, siteId?: string): Promise<void> {
|
|
siteId = siteId || CoreSites.getCurrentSiteId();
|
|
|
|
this.logger.debug(`Set status '${status}' for course ${courseId} in site ${siteId}`);
|
|
|
|
const site = await CoreSites.getSite(siteId);
|
|
let downloadTime = 0;
|
|
let previousDownloadTime = 0;
|
|
let previousStatus = '';
|
|
|
|
if (status == CoreConstants.DOWNLOADING) {
|
|
// Set download time if course is now downloading.
|
|
downloadTime = CoreTimeUtils.timestamp();
|
|
}
|
|
|
|
try {
|
|
const entry = await this.getCourseStatusData(courseId, siteId);
|
|
if (typeof downloadTime == 'undefined') {
|
|
// Keep previous download time.
|
|
downloadTime = entry.downloadTime;
|
|
previousDownloadTime = entry.previousDownloadTime;
|
|
} else {
|
|
// The downloadTime will be updated, store current time as previous.
|
|
previousDownloadTime = entry.downloadTime;
|
|
}
|
|
previousStatus = entry.status;
|
|
} catch {
|
|
// New entry.
|
|
}
|
|
|
|
if (previousStatus != status) {
|
|
// Status has changed, update it.
|
|
const data: CoreCourseStatusDBRecord = {
|
|
id: courseId,
|
|
status: status,
|
|
previous: previousStatus,
|
|
updated: new Date().getTime(),
|
|
downloadTime: downloadTime,
|
|
previousDownloadTime: previousDownloadTime,
|
|
};
|
|
|
|
await site.getDb().insertRecord(COURSE_STATUS_TABLE, data);
|
|
}
|
|
|
|
// Success inserting, trigger event.
|
|
this.triggerCourseStatusChanged(courseId, status, siteId);
|
|
}
|
|
|
|
/**
|
|
* Translate a module name to current language.
|
|
*
|
|
* @param moduleName The module name.
|
|
* @return Translated name.
|
|
*/
|
|
translateModuleName(moduleName: string): string {
|
|
if (this.CORE_MODULES.indexOf(moduleName) < 0) {
|
|
moduleName = 'external-tool';
|
|
}
|
|
|
|
const langKey = 'core.mod_' + moduleName;
|
|
const translated = Translate.instant(langKey);
|
|
|
|
return translated !== langKey ? translated : moduleName;
|
|
}
|
|
|
|
/**
|
|
* Trigger COURSE_STATUS_CHANGED with the right data.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param status New course status.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
*/
|
|
protected triggerCourseStatusChanged(courseId: number, status: string, siteId?: string): void {
|
|
CoreEvents.trigger(CoreEvents.COURSE_STATUS_CHANGED, {
|
|
courseId: courseId,
|
|
status: status,
|
|
}, siteId);
|
|
}
|
|
|
|
}
|
|
|
|
export const CoreCourse = makeSingleton(CoreCourseProvider);
|
|
|
|
/**
|
|
* Common options used by modules when calling a WS through CoreSite.
|
|
*/
|
|
export type CoreCourseCommonModWSOptions = CoreSitesCommonWSOptions & {
|
|
cmId?: number; // Module ID.
|
|
};
|
|
|
|
/**
|
|
* Data returned by course_summary_exporter.
|
|
*/
|
|
export type CoreCourseSummary = {
|
|
id: number; // Id.
|
|
fullname: string; // Fullname.
|
|
shortname: string; // Shortname.
|
|
idnumber: string; // Idnumber.
|
|
summary: string; // @since 3.3. Summary.
|
|
summaryformat: number; // @since 3.3. Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
|
|
startdate: number; // @since 3.3. Startdate.
|
|
enddate: number; // @since 3.3. Enddate.
|
|
visible: boolean; // @since 3.8. Visible.
|
|
fullnamedisplay: string; // @since 3.3. Fullnamedisplay.
|
|
viewurl: string; // Viewurl.
|
|
courseimage: string; // @since 3.6. Courseimage.
|
|
progress?: number; // @since 3.6. Progress.
|
|
hasprogress: boolean; // @since 3.6. Hasprogress.
|
|
isfavourite: boolean; // @since 3.6. Isfavourite.
|
|
hidden: boolean; // @since 3.6. Hidden.
|
|
timeaccess?: number; // @since 3.6. Timeaccess.
|
|
showshortname: boolean; // @since 3.6. Showshortname.
|
|
coursecategory: string; // @since 3.7. Coursecategory.
|
|
};
|
|
|
|
/**
|
|
* Data returned by course_module_summary_exporter.
|
|
*/
|
|
export type CoreCourseModuleSummary = {
|
|
id: number; // Id.
|
|
name: string; // Name.
|
|
url?: string; // Url.
|
|
iconurl: string; // Iconurl.
|
|
};
|
|
|
|
/**
|
|
* Params of core_completion_get_activities_completion_status WS.
|
|
*/
|
|
type CoreCompletionGetActivitiesCompletionStatusWSParams = {
|
|
courseid: number; // Course ID.
|
|
userid: number; // User ID.
|
|
};
|
|
|
|
/**
|
|
* Data returned by core_completion_get_activities_completion_status WS.
|
|
*/
|
|
export type CoreCourseCompletionActivityStatusWSResponse = {
|
|
statuses: CoreCourseCompletionActivityStatus[]; // List of activities status.
|
|
warnings?: CoreStatusWithWarningsWSResponse[];
|
|
};
|
|
|
|
/**
|
|
* Activity status.
|
|
*/
|
|
export type CoreCourseCompletionActivityStatus = {
|
|
cmid: number; // Comment ID.
|
|
modname: string; // Activity module name.
|
|
instance: number; // Instance ID.
|
|
state: number; // Completion state value: 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail.
|
|
timecompleted: number; // Timestamp for completed activity.
|
|
tracking: number; // Type of tracking: 0 means none, 1 manual, 2 automatic.
|
|
overrideby?: number; // The user id who has overriden the status, or null.
|
|
valueused?: boolean; // Whether the completion status affects the availability of another activity.
|
|
offline?: boolean; // Whether the completions is offline and not yet synced.
|
|
};
|
|
|
|
/**
|
|
* Params of core_block_get_course_blocks WS.
|
|
*/
|
|
type CoreBlockGetCourseBlocksWSParams = {
|
|
courseid: number; // Course id.
|
|
returncontents?: boolean; // Whether to return the block contents.
|
|
};
|
|
|
|
/**
|
|
* Data returned by core_block_get_course_blocks WS.
|
|
*/
|
|
export type CoreCourseBlocksWSResponse = {
|
|
blocks: CoreCourseBlock[]; // List of blocks in the course.
|
|
warnings?: CoreStatusWithWarningsWSResponse[];
|
|
};
|
|
|
|
/**
|
|
* Block data type.
|
|
*/
|
|
export type CoreCourseBlock = {
|
|
instanceid: number; // Block instance id.
|
|
name: string; // Block name.
|
|
region: string; // Block region.
|
|
positionid: number; // Position id.
|
|
collapsible: boolean; // Whether the block is collapsible.
|
|
dockable: boolean; // Whether the block is dockable.
|
|
weight?: number; // Used to order blocks within a region.
|
|
visible?: boolean; // Whether the block is visible.
|
|
contents?: {
|
|
title: string; // Block title.
|
|
content: string; // Block contents.
|
|
contentformat: number; // Content format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
|
|
footer: string; // Block footer.
|
|
files: CoreWSExternalFile[];
|
|
}; // Block contents (if required).
|
|
configs?: { // Block instance and plugin configuration settings.
|
|
name: string; // Name.
|
|
value: string; // JSON encoded representation of the config value.
|
|
type: string; // Type (instance or plugin).
|
|
}[];
|
|
configsRecord?: Record<string, { // Block instance and plugin configuration settings.
|
|
name: string; // Name.
|
|
value: string; // JSON encoded representation of the config value.
|
|
type: string; // Type (instance or plugin).
|
|
}>;
|
|
};
|
|
|
|
/**
|
|
* Params of core_course_get_contents WS.
|
|
*/
|
|
export type CoreCourseGetContentsParams = {
|
|
courseid: number; // Course id.
|
|
options?: { // Options, used since Moodle 2.9.
|
|
/**
|
|
* The expected keys (value format) are:
|
|
*
|
|
* excludemodules (bool) Do not return modules, return only the sections structure
|
|
* excludecontents (bool) Do not return module contents (i.e: files inside a resource)
|
|
* includestealthmodules (bool) Return stealth modules for students in a special
|
|
* section (with id -1)
|
|
* sectionid (int) Return only this section
|
|
* sectionnumber (int) Return only this section with number (order)
|
|
* cmid (int) Return only this module information (among the whole sections structure)
|
|
* modname (string) Return only modules with this name "label, forum, etc..."
|
|
* modid (int) Return only the module with this id (to be used with modname.
|
|
*/
|
|
name: string;
|
|
value: string | number | boolean; // The value of the option, this param is personaly validated in the external function.
|
|
}[];
|
|
};
|
|
|
|
/**
|
|
* Data returned by core_course_get_contents WS.
|
|
*/
|
|
export type CoreCourseWSSection = {
|
|
id: number; // Section ID.
|
|
name: string; // Section name.
|
|
visible?: number; // Is the section visible.
|
|
summary: string; // Section description.
|
|
summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
|
|
section?: number; // Section number inside the course.
|
|
hiddenbynumsections?: number; // Whether is a section hidden in the course format.
|
|
uservisible?: boolean; // Is the section visible for the user?.
|
|
availabilityinfo?: string; // Availability information.
|
|
modules: CoreCourseWSModule[];
|
|
};
|
|
|
|
/**
|
|
* Params of core_course_get_course_module WS.
|
|
*/
|
|
type CoreCourseGetCourseModuleWSParams = {
|
|
cmid: number; // The course module id.
|
|
};
|
|
|
|
/**
|
|
* Params of core_course_get_course_module_by_instance WS.
|
|
*/
|
|
type CoreCourseGetCourseModuleByInstanceWSParams = {
|
|
module: string; // The module name.
|
|
instance: number; // The module instance id.
|
|
};
|
|
|
|
/**
|
|
* Data returned by core_course_get_course_module and core_course_get_course_module_by_instance WS.
|
|
*/
|
|
export type CoreCourseGetCourseModuleWSResponse = {
|
|
cm: CoreCourseModuleBasicInfo;
|
|
warnings?: CoreWSExternalWarning[];
|
|
};
|
|
|
|
/**
|
|
* Course module data returned by the WS.
|
|
*/
|
|
export type CoreCourseWSModule = {
|
|
id: number; // Activity id.
|
|
course?: number; // The course id.
|
|
url?: string; // Activity url.
|
|
name: string; // Activity module name.
|
|
instance?: number; // Instance id.
|
|
contextid?: number; // Activity context id.
|
|
description?: string; // Activity description.
|
|
visible?: number; // Is the module visible.
|
|
uservisible?: boolean; // Is the module visible for the user?.
|
|
availabilityinfo?: string; // Availability information.
|
|
visibleoncoursepage?: number; // Is the module visible on course page.
|
|
modicon: string; // Activity icon url.
|
|
modname: string; // Activity module type.
|
|
modplural: string; // Activity module plural name.
|
|
availability?: string; // Module availability settings.
|
|
indent: number; // Number of identation in the site.
|
|
onclick?: string; // Onclick action.
|
|
afterlink?: string; // After link info to be displayed.
|
|
customdata?: string; // Custom data (JSON encoded).
|
|
noviewlink?: boolean; // Whether the module has no view page.
|
|
completion?: number; // Type of completion tracking: 0 means none, 1 manual, 2 automatic.
|
|
completiondata?: CoreCourseModuleWSCompletionData; // Module completion data.
|
|
contents: CoreCourseModuleContentFile[];
|
|
contentsinfo?: { // Contents summary information.
|
|
filescount: number; // Total number of files.
|
|
filessize: number; // Total files size.
|
|
lastmodified: number; // Last time files were modified.
|
|
mimetypes: string[]; // Files mime types.
|
|
repositorytype?: string; // The repository type for the main file.
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Module completion data.
|
|
*/
|
|
export type CoreCourseModuleWSCompletionData = {
|
|
state: number; // Completion state value: 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail.
|
|
timecompleted: number; // Timestamp for completion status.
|
|
overrideby: number; // The user id who has overriden the status.
|
|
valueused?: boolean; // Whether the completion status affects the availability of another activity.
|
|
};
|
|
|
|
export type CoreCourseModuleContentFile = {
|
|
// Common properties with CoreWSExternalFile.
|
|
filename: string; // Filename.
|
|
filepath: string; // Filepath.
|
|
filesize: number; // Filesize.
|
|
fileurl: string; // Downloadable file url.
|
|
timemodified: number; // Time modified.
|
|
mimetype?: string; // File mime type.
|
|
isexternalfile?: number; // Whether is an external file.
|
|
repositorytype?: string; // The repository type for external files.
|
|
|
|
type: string; // A file or a folder or external link.
|
|
content?: string; // Raw content, will be used when type is content.
|
|
timecreated: number; // Time created.
|
|
sortorder: number; // Content sort order.
|
|
userid: number; // User who added this content to moodle.
|
|
author: string; // Content owner.
|
|
license: string; // Content license.
|
|
tags?: { // Tags.
|
|
id: number; // Tag id.
|
|
name: string; // Tag name.
|
|
rawname: string; // The raw, unnormalised name for the tag as entered by users.
|
|
isstandard: boolean; // Whether this tag is standard.
|
|
tagcollid: number; // Tag collection id.
|
|
taginstanceid: number; // Tag instance id.
|
|
taginstancecontextid: number; // Context the tag instance belongs to.
|
|
itemid: number; // Id of the record tagged.
|
|
ordering: number; // Tag ordering.
|
|
flag: number; // Whether the tag is flagged as inappropriate.
|
|
}[];
|
|
};
|
|
|
|
/**
|
|
* Course module basic info type. 3.2 onwards.
|
|
*/
|
|
export type CoreCourseModuleGradeInfo = {
|
|
grade?: number; // Grade (max value or scale id).
|
|
scale?: string; // Scale items (if used).
|
|
gradepass?: string; // Grade to pass (float).
|
|
gradecat?: number; // Grade category.
|
|
advancedgrading?: CoreCourseModuleAdvancedGradingSetting[]; // Advanced grading settings.
|
|
outcomes?: CoreCourseModuleGradeOutcome[];
|
|
};
|
|
|
|
/**
|
|
* Advanced grading settings.
|
|
*/
|
|
export type CoreCourseModuleAdvancedGradingSetting = {
|
|
area: string; // Gradable area name.
|
|
method: string; // Grading method.
|
|
};
|
|
|
|
/**
|
|
* Grade outcome information.
|
|
*/
|
|
export type CoreCourseModuleGradeOutcome = {
|
|
id: string; // Outcome id.
|
|
name: string; // Outcome full name.
|
|
scale: string; // Scale items.
|
|
};
|
|
|
|
/**
|
|
* Course module basic info type.
|
|
*/
|
|
export type CoreCourseModuleBasicInfo = CoreCourseModuleGradeInfo & {
|
|
id: number; // The course module id.
|
|
course: number; // The course id.
|
|
module: number; // The module type id.
|
|
name: string; // The activity name.
|
|
modname: string; // The module component name (forum, assign, etc..).
|
|
instance: number; // The activity instance id.
|
|
section: number; // The module section id.
|
|
sectionnum: number; // The module section number.
|
|
groupmode: number; // Group mode.
|
|
groupingid: number; // Grouping id.
|
|
completion: number; // If completion is enabled.
|
|
idnumber?: string; // Module id number.
|
|
added?: number; // Time added.
|
|
score?: number; // Score.
|
|
indent?: number; // Indentation.
|
|
visible?: number; // If visible.
|
|
visibleoncoursepage?: number; // If visible on course page.
|
|
visibleold?: number; // Visible old.
|
|
completiongradeitemnumber?: number; // Completion grade item.
|
|
completionview?: number; // Completion view setting.
|
|
completionexpected?: number; // Completion time expected.
|
|
showdescription?: number; // If the description is showed.
|
|
availability?: string; // Availability settings.
|
|
};
|
|
|
|
/**
|
|
* Params of core_course_view_course WS.
|
|
*/
|
|
type CoreCourseViewCourseWSParams = {
|
|
courseid: number; // Id of the course.
|
|
sectionnumber?: number; // Section number.
|
|
};
|
|
|
|
/**
|
|
* Params of core_completion_update_activity_completion_status_manually WS.
|
|
*/
|
|
type CoreCompletionUpdateActivityCompletionStatusManuallyWSParams = {
|
|
cmid: number; // Course module id.
|
|
completed: boolean; // Activity completed or not.
|
|
};
|
|
|
|
/**
|
|
* Any of the possible module WS data.
|
|
*/
|
|
export type CoreCourseAnyModuleData = CoreCourseWSModule | CoreCourseModuleBasicInfo & {
|
|
contents?: CoreCourseModuleContentFile[]; // Calculated in the app in loadModuleContents.
|
|
};
|