1860 lines
66 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 { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { makeSingleton } from '@singletons';
import { CoreStatusWithWarningsWSResponse, CoreWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
import { CoreEvents } from '@singletons/events';
import { CoreWSError } from '@classes/errors/wserror';
import { CoreCourseAnyCourseDataWithExtraInfoAndOptions, CoreCourseWithImageAndColor } from './courses-helper';
import { asyncObservable, firstValueFrom, ignoreErrors, zipIncludingComplete } from '@/core/utils/rxjs';
import { of, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
const ROOT_CACHE_KEY = 'mmCourses:';
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 {
[CoreCoursesProvider.EVENT_MY_COURSES_CHANGED]: CoreCoursesMyCoursesChangedEventData;
[CoreCoursesProvider.EVENT_MY_COURSES_UPDATED]: CoreCoursesMyCoursesUpdatedEventData;
[CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED]: CoreCoursesDashboardDownloadEnabledChangedEventData;
}
}
/**
* Service that provides some features regarding lists of courses and categories.
*/
@Injectable({ providedIn: 'root' })
export class CoreCoursesProvider {
static readonly SEARCH_PER_PAGE = 20;
static readonly RECENT_PER_PAGE = 10;
static readonly ENROL_INVALID_KEY = 'CoreCoursesEnrolInvalidKey';
static readonly EVENT_MY_COURSES_CHANGED = 'courses_my_courses_changed'; // User course list changed while app is running.
// A course was hidden/favourite, or user enroled in a course.
static readonly EVENT_MY_COURSES_UPDATED = 'courses_my_courses_updated';
static readonly EVENT_MY_COURSES_REFRESHED = 'courses_my_courses_refreshed';
static readonly EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED = 'dashboard_download_enabled_changed';
// Actions for event EVENT_MY_COURSES_UPDATED.
static readonly ACTION_ENROL = 'enrol'; // User enrolled in a course.
static readonly ACTION_STATE_CHANGED = 'state_changed'; // Course state changed (hidden, favourite).
static readonly ACTION_VIEW = 'view'; // Course viewed.
// Possible states changed.
static readonly STATE_HIDDEN = 'hidden';
static readonly STATE_FAVOURITE = 'favourite';
protected userCoursesIds?: Set<number>;
protected downloadOptionsEnabled = false;
/**
* Whether current site supports getting course options.
*
* @return Whether current site supports getting course options.
* @deprecated since app 4.0
*/
canGetAdminAndNavOptions(): boolean {
return true;
}
/**
* Get categories. They can be filtered by id.
*
* @param categoryId Category ID to get.
* @param addSubcategories If it should add subcategories to the list.
* @param siteId Site to get the courses from. If not defined, use current site.
* @return Promise resolved with the categories.
*/
async getCategories(
categoryId: number,
addSubcategories: boolean = false,
siteId?: string,
): Promise<CoreCourseGetCategoriesWSResponse> {
const site = await CoreSites.getSite(siteId);
// Get parent when id is the root category.
const criteriaKey = categoryId == 0 ? 'parent' : 'id';
const params: CoreCourseGetCategoriesWSParams = {
criteria: [
{
key: criteriaKey,
value: categoryId,
},
],
addsubcategories: addSubcategories,
};
const preSets = {
cacheKey: this.getCategoriesCacheKey(categoryId, addSubcategories),
updateFrequency: CoreSite.FREQUENCY_RARELY,
};
return site.read('core_course_get_categories', params, preSets);
}
/**
* Get cache key for get categories methods WS call.
*
* @param categoryId Category ID to get.
* @param addSubcategories If add subcategories to the list.
* @return Cache key.
*/
protected getCategoriesCacheKey(categoryId: number, addSubcategories?: boolean): string {
return ROOT_CACHE_KEY + 'categories:' + categoryId + ':' + !!addSubcategories;
}
/**
* Given a list of course IDs to get course admin and nav options, return the list of courseIds to use.
*
* @param courseIds Course IDs.
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved with the list of course IDs.
*/
protected async getCourseIdsForAdminAndNavOptions(courseIds: number[], siteId?: string): Promise<number[]> {
const site = await CoreSites.getSite(siteId);
const siteHomeId = site.getSiteHomeId();
if (courseIds.length == 1) {
// Only 1 course, check if it belongs to the user courses. If so, use all user courses.
return this.getCourseIdsIfEnrolled(courseIds[0], siteId);
} else {
if (courseIds.length > 1 && courseIds.indexOf(siteHomeId) == -1) {
courseIds.push(siteHomeId);
}
// Sort the course IDs.
courseIds.sort((a, b) => b - a);
return courseIds;
}
}
/**
* Given a course ID, if user is enrolled in the course it will return the IDs of all enrolled courses and site home.
* Return only the course ID otherwise.
*
* @param courseIds Course IDs.
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved with the list of course IDs.
*/
async getCourseIdsIfEnrolled(courseId: number, siteId?: string): Promise<number[]> {
const site = await CoreSites.getSite(siteId);
const siteHomeId = site.getSiteHomeId();
try {
// Check if user is enrolled in the course.
const courses = await this.getUserCourses(true, siteId);
let useAllCourses = false;
if (courseId == siteHomeId) {
// It's site home, use all courses.
useAllCourses = true;
} else {
useAllCourses = !!courses.find((course) => course.id == courseId);
}
if (useAllCourses) {
// User is enrolled, return all the courses.
const courseIds = courses.map((course) => course.id);
// Always add the site home ID.
courseIds.push(siteHomeId);
// Sort the course IDs.
courseIds.sort((a, b) => b - a);
return courseIds;
}
} catch {
// Ignore errors.
}
return [courseId];
}
/**
* Check if download a whole course is disabled in a certain site.
*
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved with true if disabled, rejected or resolved with false otherwise.
*/
async isDownloadCourseDisabled(siteId?: string): Promise<boolean> {
const site = await CoreSites.getSite(siteId);
return this.isDownloadCoursesDisabledInSite(site);
}
/**
* Check if download a whole course is disabled in a certain site.
*
* @param site Site. If not defined, use current site.
* @return Whether it's disabled.
*/
isDownloadCourseDisabledInSite(site?: CoreSite): boolean {
site = site || CoreSites.getCurrentSite();
return !site || site.isOfflineDisabled() || site.isFeatureDisabled('NoDelegate_CoreCourseDownload');
}
/**
* Check if download all courses is disabled in a certain site.
*
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved with true if disabled, rejected or resolved with false otherwise.
*/
async isDownloadCoursesDisabled(siteId?: string): Promise<boolean> {
const site = await CoreSites.getSite(siteId);
return this.isDownloadCoursesDisabledInSite(site);
}
/**
* Check if download all courses is disabled in a certain site.
*
* @param site Site. If not defined, use current site.
* @return Whether it's disabled.
*/
isDownloadCoursesDisabledInSite(site?: CoreSite): boolean {
site = site || CoreSites.getCurrentSite();
return !site || site.isOfflineDisabled() || site.isFeatureDisabled('NoDelegate_CoreCoursesDownload');
}
/**
* Check if My Courses is disabled in a certain site.
*
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved with true if disabled, rejected or resolved with false otherwise.
*/
async isMyCoursesDisabled(siteId?: string): Promise<boolean> {
const site = await CoreSites.getSite(siteId);
return this.isMyCoursesDisabledInSite(site);
}
/**
* Check if My Courses is disabled in a certain site.
*
* @param site Site. If not defined, use current site.
* @return Whether it's disabled.
*/
isMyCoursesDisabledInSite(site?: CoreSite): boolean {
site = site || CoreSites.getCurrentSite();
return !site || site.isFeatureDisabled('CoreMainMenuDelegate_CoreCourses');
}
/**
* Check if Search Courses is disabled in a certain site.
*
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved with true if disabled, rejected or resolved with false otherwise.
*/
async isSearchCoursesDisabled(siteId?: string): Promise<boolean> {
const site = await CoreSites.getSite(siteId);
return this.isSearchCoursesDisabledInSite(site);
}
/**
* Check if Search Courses is disabled in a certain site.
*
* @param site Site. If not defined, use current site.
* @return Whether it's disabled.
*/
isSearchCoursesDisabledInSite(site?: CoreSite): boolean {
site = site || CoreSites.getCurrentSite();
return !site || site.isFeatureDisabled('CoreCourseOptionsDelegate_search');
}
/**
* Get course.
*
* @param id ID of the course to get.
* @param siteId Site to get the courses from. If not defined, use current site.
* @return Promise resolved with the course.
*/
async getCourse(id: number, siteId?: string): Promise<CoreCourseGetCoursesData> {
const courses = await this.getCourses([id], siteId);
if (courses && courses.length > 0) {
return courses[0];
}
throw Error('Course not found on core_course_get_courses');
}
/**
* Get the enrolment methods from a course.
*
* @param id ID of the course.
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved with the methods.
*/
async getCourseEnrolmentMethods(id: number, siteId?: string): Promise<CoreCourseEnrolmentMethod[]> {
const site = await CoreSites.getSite(siteId);
const params: CoreEnrolGetCourseEnrolmentMethodsWSParams = {
courseid: id,
};
const preSets = {
cacheKey: this.getCourseEnrolmentMethodsCacheKey(id),
updateFrequency: CoreSite.FREQUENCY_RARELY,
};
return site.read('core_enrol_get_course_enrolment_methods', params, preSets);
}
/**
* Get cache key for get course enrolment methods WS call.
*
* @param id Course ID.
* @return Cache key.
*/
protected getCourseEnrolmentMethodsCacheKey(id: number): string {
return ROOT_CACHE_KEY + 'enrolmentmethods:' + id;
}
/**
* Get info from a course guest enrolment method.
*
* @param instanceId Guest instance ID.
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved when the info is retrieved.
*/
async getCourseGuestEnrolmentInfo(instanceId: number, siteId?: string): Promise<CoreCourseEnrolmentGuestMethod> {
const site = await CoreSites.getSite(siteId);
const params: EnrolGuestGetInstanceInfoWSParams = {
instanceid: instanceId,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getCourseGuestEnrolmentInfoCacheKey(instanceId),
updateFrequency: CoreSite.FREQUENCY_RARELY,
};
const response = await site.read<EnrolGuestGetInstanceInfoWSResponse>('enrol_guest_get_instance_info', params, preSets);
return response.instanceinfo;
}
/**
* Get cache key for get course guest enrolment methods WS call.
*
* @param instanceId Guest instance ID.
* @return Cache key.
*/
protected getCourseGuestEnrolmentInfoCacheKey(instanceId: number): string {
return ROOT_CACHE_KEY + 'guestinfo:' + instanceId;
}
/**
* Get courses.
* Warning: if the user doesn't have permissions to view some of the courses passed the WS call will fail.
* The user must be able to view ALL the courses passed.
*
* @param ids List of IDs of the courses to get.
* @param siteId Site to get the courses from. If not defined, use current site.
* @return Promise resolved with the courses.
*/
async getCourses(ids: number[], siteId?: string): Promise<CoreCourseGetCoursesWSResponse> {
if (!Array.isArray(ids)) {
throw Error('ids parameter should be an array');
}
if (ids.length === 0) {
return [];
}
const site = await CoreSites.getSite(siteId);
const params: CoreCourseGetCoursesWSParams = {
options: {
ids: ids,
},
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getCoursesCacheKey(ids),
updateFrequency: CoreSite.FREQUENCY_RARELY,
};
return site.read('core_course_get_courses', params, preSets);
}
/**
* Get cache key for get courses WS call.
*
* @param ids Courses IDs.
* @return Cache key.
*/
protected getCoursesCacheKey(ids: number[]): string {
return ROOT_CACHE_KEY + 'course:' + JSON.stringify(ids);
}
/**
* This function is meant to decrease WS calls.
* When requesting a single course that belongs to enrolled courses, request all enrolled courses because
* the WS call is probably cached.
*
* @param field The field to search.
* @param value The value to match.
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved with the field and value to use.
*/
protected async fixCoursesByFieldParams(
field: string = '',
value: number | string = '',
siteId?: string,
): Promise<{ field: string; value: number | string }> {
if (field == 'id' || field == 'ids') {
let courseIds: number[];
if (typeof value == 'string') {
courseIds = value.split(',').map((id) => parseInt(id, 10));
} else {
courseIds = [value];
}
// Use the same optimization as in get admin and nav options. This will return the course IDs to use.
courseIds = await this.getCourseIdsForAdminAndNavOptions(courseIds, siteId);
if (courseIds.length > 1) {
return { field: 'ids', value: courseIds.join(',') };
} else {
return { field: 'id', value: Number(courseIds[0]) };
}
} else {
// Nothing to do.
return { field: field, value: value };
}
}
/**
* Get the first course returned by getCoursesByField.
*
* @param field The field to search. Can be left empty for all courses or:
* id: course id.
* ids: comma separated course ids.
* shortname: course short name.
* idnumber: course id number.
* category: category id the course belongs to.
* @param value The value to match.
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved with the first course.
*/
async getCourseByField(field?: string, value?: string | number, siteId?: string): Promise<CoreCourseSearchedData> {
const courses = await this.getCoursesByField(field, value, siteId);
if (courses && courses.length > 0) {
return courses[0];
}
throw Error('Course not found on core_course_get_courses_by_field');
}
/**
* Get courses. They can be filtered by field.
*
* @param field The field to search. Can be left empty for all courses or:
* id: course id.
* ids: comma separated course ids.
* shortname: course short name.
* idnumber: course id number.
* category: category id the course belongs to.
* @param value The value to match.
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved with the courses.
*/
getCoursesByField(
field: string = '',
value: string | number = '',
siteId?: string,
): Promise<CoreCourseSearchedData[]> {
return firstValueFrom(this.getCoursesByFieldObservable(field, value, { siteId }));
}
/**
* Get courses. They can be filtered by field.
*
* @param field The field to search. Can be left empty for all courses or:
* id: course id.
* ids: comma separated course ids.
* shortname: course short name.
* idnumber: course id number.
* category: category id the course belongs to.
* @param value The value to match.
* @param options Other options.
* @return Observable that returns the courses.
*/
getCoursesByFieldObservable(
field: string = '',
value: string | number = '',
options: CoreSitesCommonWSOptions = {},
): Observable<CoreCourseSearchedData[]> {
return asyncObservable(async () => {
const siteId = options.siteId || CoreSites.getCurrentSiteId();
const originalValue = value;
const site = await CoreSites.getSite(siteId);
// Fix params. Tries to use cached data, no need to use observer.
const fieldParams = await this.fixCoursesByFieldParams(field, value, siteId);
const hasChanged = fieldParams.field != field || fieldParams.value != value;
field = fieldParams.field;
value = fieldParams.value;
const data: CoreCourseGetCoursesByFieldWSParams = {
field: field,
value: field ? value : '',
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getCoursesByFieldCacheKey(field, value),
updateFrequency: CoreSite.FREQUENCY_RARELY,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy),
};
const observable = site.readObservable<CoreCourseGetCoursesByFieldWSResponse>(
'core_course_get_courses_by_field',
data,
preSets,
);
return observable.pipe(map(response => {
if (!response.courses) {
throw Error('WS core_course_get_courses_by_field failed');
}
if (field == 'ids' && hasChanged) {
// The list of courses requestes was changed to optimize it.
// Return only the ones that were being requested.
const courseIds = String(originalValue).split(',').map((id) => parseInt(id, 10));
// Only courses from the original selection.
response.courses = response.courses.filter((course) => courseIds.indexOf(course.id) >= 0);
}
// Courses will be sorted using sortorder if available.
return response.courses.sort((a, b) => {
if (a.sortorder === undefined && b.sortorder === undefined) {
return b.id - a.id;
}
if (a.sortorder === undefined) {
return 1;
}
if (b.sortorder === undefined) {
return -1;
}
return a.sortorder - b.sortorder;
});
}));
});
}
/**
* Get cache key for get courses WS call.
*
* @param field The field to search.
* @param value The value to match.
* @return Cache key.
*/
protected getCoursesByFieldCacheKey(field: string = '', value: string | number = ''): string {
return ROOT_CACHE_KEY + 'coursesbyfield:' + field + ':' + value;
}
/**
* Get courses matching the given custom field. By default it will try not to use cache.
*
* @param customFieldName Custom field name.
* @param customFieldValue Custom field value.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the list of courses.
* @since 3.8
*/
getEnrolledCoursesByCustomField(
customFieldName: string,
customFieldValue: string,
siteId?: string,
): Promise<CoreCourseSummaryData[]> {
return firstValueFrom(this.getEnrolledCoursesByCustomFieldObservable(customFieldName, customFieldValue, {
readingStrategy: CoreSitesReadingStrategy.PREFER_NETWORK,
siteId,
}));
}
/**
* Get courses matching the given custom field.
*
* @param customFieldName Custom field name.
* @param customFieldValue Custom field value.
* @param options Common options.
* @return Promise resolved with the list of courses.
* @since 3.8
*/
getEnrolledCoursesByCustomFieldObservable(
customFieldName: string,
customFieldValue: string,
options: CoreSitesCommonWSOptions,
): Observable<CoreCourseSummaryData[]> {
return asyncObservable(async () => {
const site = await CoreSites.getSite(options. siteId);
const params: CoreCourseGetEnrolledCoursesByTimelineClassificationWSParams = {
classification: 'customfield',
customfieldname: customFieldName,
customfieldvalue: customFieldValue,
};
const preSets: CoreSiteWSPreSets = {
...CoreSites.getReadingStrategyPreSets(options.readingStrategy ?? CoreSitesReadingStrategy.PREFER_NETWORK),
};
return site.readObservable<CoreCourseGetEnrolledCoursesByTimelineClassificationWSResponse>(
'core_course_get_enrolled_courses_by_timeline_classification',
params,
preSets,
).pipe(map(response => response.courses));
});
}
/**
* Check if get courses by field WS is available in a certain site.
*
* @return Whether get courses by field is available.
* @deprecated since app 4.0
*/
isGetCoursesByFieldAvailable(): boolean {
return true;
}
/**
* Check if get courses by field WS is available in a certain site, by site ID.
*
* @return Promise resolved with boolean: whether get courses by field is available.
* @deprecated since app 4.0
*/
async isGetCoursesByFieldAvailableInSite(): Promise<boolean> {
return true;
}
/**
* Get the navigation and administration options for the given courses.
*
* @param courseIds IDs of courses to get.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the options for each course.
*/
getCoursesAdminAndNavOptions(
courseIds: number[],
siteId?: string,
): Promise<{
navOptions: CoreCourseUserAdminOrNavOptionCourseIndexed;
admOptions: CoreCourseUserAdminOrNavOptionCourseIndexed;
}> {
return firstValueFrom(this.getCoursesAdminAndNavOptionsObservable(courseIds, { siteId }));
}
/**
* Get the navigation and administration options for the given courses.
*
* @param courseIds IDs of courses to get.
* @param options Options.
* @return Observable that returns the options for each course.
*/
getCoursesAdminAndNavOptionsObservable(
courseIds: number[],
options: CoreSitesCommonWSOptions = {},
): Observable<{
navOptions: CoreCourseUserAdminOrNavOptionCourseIndexed;
admOptions: CoreCourseUserAdminOrNavOptionCourseIndexed;
}> {
return asyncObservable(async () => {
const siteId = options.siteId || CoreSites.getCurrentSiteId();
// Get the list of courseIds to use based on the param. Tries to use cached data, no need to use observer.
courseIds = await this.getCourseIdsForAdminAndNavOptions(courseIds, siteId);
// Get user navigation and administration options.
return zipIncludingComplete(
ignoreErrors(this.getUserNavigationOptionsObservable(courseIds, options), {}),
ignoreErrors(this.getUserAdministrationOptionsObservable(courseIds, options), {}),
).pipe(
map(([navOptions, admOptions]) => ({ navOptions, admOptions })),
);
});
}
/**
* Get cache key for get recent courses WS call.
*
* @param userId User ID.
* @return Cache key.
*/
protected getRecentCoursesCacheKey(userId: number): string {
return `${ROOT_CACHE_KEY}:recentcourses:${userId}`;
}
/**
* Get recent courses.
*
* @param options Options.
* @return Promise resolved with courses.
* @since 3.6
*/
async getRecentCourses(options: CoreCourseGetRecentCoursesOptions = {}): Promise<CoreCourseSummaryData[]> {
const site = await CoreSites.getSite(options.siteId);
const userId = options.userId || site.getUserId();
const params: CoreCourseGetRecentCoursesWSParams = {
userid: userId,
offset: options.offset || 0,
limit: options.limit || CoreCoursesProvider.RECENT_PER_PAGE,
sort: options.sort,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getRecentCoursesCacheKey(userId),
};
return site.read<CoreCourseSummaryData[]>('core_course_get_recent_courses', params, preSets);
}
/**
* Get the common part of the cache keys for user administration options WS calls.
*
* @return Cache key.
*/
protected getUserAdministrationOptionsCommonCacheKey(): string {
return ROOT_CACHE_KEY + 'administrationOptions:';
}
/**
* Get cache key for get user administration options WS call.
*
* @param courseIds IDs of courses to get.
* @return Cache key.
*/
protected getUserAdministrationOptionsCacheKey(courseIds: number[]): string {
return this.getUserAdministrationOptionsCommonCacheKey() + courseIds.join(',');
}
/**
* Get user administration options for a set of courses.
*
* @param courseIds IDs of courses to get.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with administration options for each course.
*/
getUserAdministrationOptions(courseIds: number[], siteId?: string): Promise<CoreCourseUserAdminOrNavOptionCourseIndexed> {
return firstValueFrom(this.getUserAdministrationOptionsObservable(courseIds, { siteId }));
}
/**
* Get user administration options for a set of courses.
*
* @param courseIds IDs of courses to get.
* @param options Options.
* @return Observable that returns administration options for each course.
*/
getUserAdministrationOptionsObservable(
courseIds: number[],
options: CoreSitesCommonWSOptions = {},
): Observable<CoreCourseUserAdminOrNavOptionCourseIndexed> {
if (!courseIds || courseIds.length == 0) {
return of({});
}
return asyncObservable(async () => {
const site = await CoreSites.getSite(options.siteId);
const params: CoreCourseGetUserAdminOrNavOptionsWSParams = {
courseids: courseIds,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getUserAdministrationOptionsCacheKey(courseIds),
updateFrequency: CoreSite.FREQUENCY_RARELY,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy),
};
const observable = site.readObservable<CoreCourseGetUserAdminOrNavOptionsWSResponse>(
'core_course_get_user_administration_options',
params,
preSets,
);
// Format returned data.
return observable.pipe(map(response => this.formatUserAdminOrNavOptions(response.courses)));
});
}
/**
* Get the common part of the cache keys for user navigation options WS calls.
*
* @return Cache key.
*/
protected getUserNavigationOptionsCommonCacheKey(): string {
return ROOT_CACHE_KEY + 'navigationOptions:';
}
/**
* Get cache key for get user navigation options WS call.
*
* @return Cache key.
*/
protected getUserNavigationOptionsCacheKey(courseIds: number[]): string {
return this.getUserNavigationOptionsCommonCacheKey() + courseIds.join(',');
}
/**
* Get user navigation options for a set of courses.
*
* @param courseIds IDs of courses to get.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with navigation options for each course.
*/
async getUserNavigationOptions(courseIds: number[], siteId?: string): Promise<CoreCourseUserAdminOrNavOptionCourseIndexed> {
return firstValueFrom(this.getUserNavigationOptionsObservable(courseIds, { siteId }));
}
/**
* Get user navigation options for a set of courses.
*
* @param courseIds IDs of courses to get.
* @param options Options.
* @return Observable that returns navigation options for each course.
*/
getUserNavigationOptionsObservable(
courseIds: number[],
options: CoreSitesCommonWSOptions = {},
): Observable<CoreCourseUserAdminOrNavOptionCourseIndexed> {
if (!courseIds || courseIds.length == 0) {
return of({});
}
return asyncObservable(async () => {
const site = await CoreSites.getSite(options.siteId);
const params: CoreCourseGetUserAdminOrNavOptionsWSParams = {
courseids: courseIds,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getUserNavigationOptionsCacheKey(courseIds),
updateFrequency: CoreSite.FREQUENCY_RARELY,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy),
};
const observable = site.readObservable<CoreCourseGetUserAdminOrNavOptionsWSResponse>(
'core_course_get_user_navigation_options',
params,
preSets,
);
// Format returned data.
return observable.pipe(map(response => this.formatUserAdminOrNavOptions(response.courses)));
});
}
/**
* Format user navigation or administration options.
*
* @param courses Navigation or administration options for each course.
* @return Formatted options.
*/
protected formatUserAdminOrNavOptions(courses: CoreCourseUserAdminOrNavOption[]): CoreCourseUserAdminOrNavOptionCourseIndexed {
const result = {};
courses.forEach((course) => {
const options = {};
if (course.options) {
course.options.forEach((option) => {
options[option.name] = option.available;
});
}
result[course.id] = options;
});
return result;
}
/**
* Get a course the user is enrolled in. This function relies on getUserCourses.
* preferCache=true will try to speed up the response, but the data returned might not be updated.
*
* @param id ID of the course to get.
* @param preferCache True if shouldn't call WS if data is cached, false otherwise.
* @param siteId Site to get the courses from. If not defined, use current site.
* @return Promise resolved with the course.
*/
async getUserCourse(id: number, preferCache?: boolean, siteId?: string): Promise<CoreEnrolledCourseData> {
if (!id) {
throw Error('Invalid id parameter on getUserCourse');
}
const courses = await this.getUserCourses(preferCache, siteId);
const course = courses.find((course) => course.id == id);
if (course) {
return course;
}
throw Error('Course not found on core_enrol_get_users_courses');
}
/**
* Get user courses.
*
* @param preferCache True if shouldn't call WS if data is cached, false otherwise.
* @param siteId Site to get the courses from. If not defined, use current site.
* @param strategy Reading strategy.
* @return Promise resolved with the courses.
*/
getUserCourses(
preferCache: boolean = false,
siteId?: string,
strategy?: CoreSitesReadingStrategy,
): Promise<CoreEnrolledCourseData[]> {
strategy = strategy ?? (preferCache ? CoreSitesReadingStrategy.PREFER_CACHE : undefined);
return this.getUserCoursesObservable({
readingStrategy: strategy,
siteId,
}).toPromise();
}
/**
* Get user courses.
*
* @param options Options.
* @return Observable that returns the courses.
*/
getUserCoursesObservable(options: CoreSitesCommonWSOptions = {}): Observable<CoreEnrolledCourseData[]> {
return asyncObservable(async () => {
const site = await CoreSites.getSite(options.siteId);
const userId = site.getUserId();
const wsParams: CoreEnrolGetUsersCoursesWSParams = {
userid: userId,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getUserCoursesCacheKey(),
getCacheUsingCacheKey: true,
updateFrequency: CoreSite.FREQUENCY_RARELY,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy),
};
if (site.isVersionGreaterEqualThan('3.7')) {
wsParams.returnusercount = false;
}
const observable = site.readObservable<CoreEnrolGetUsersCoursesWSResponse>(
'core_enrol_get_users_courses',
wsParams,
preSets,
);
return observable.pipe(map(courses => {
if (this.userCoursesIds) {
// Check if the list of courses has changed.
const added: number[] = [];
const removed: number[] = [];
const previousIds = this.userCoursesIds;
const currentIds = new Set<number>();
courses.forEach((course) => {
// Move category field to categoryid on a course.
course.categoryid = course.category;
delete course.category;
currentIds.add(course.id);
if (!previousIds.has(course.id)) {
// Course added.
added.push(course.id);
}
});
if (courses.length - added.length !== previousIds.size) {
// A course was removed, check which one.
previousIds.forEach((id) => {
if (!currentIds.has(id)) {
// Course removed.
removed.push(Number(id));
}
});
}
if (added.length || removed.length) {
// At least 1 course was added or removed, trigger the event.
CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_CHANGED, {
added: added,
removed: removed,
}, site.getId());
}
this.userCoursesIds = currentIds;
} else {
const coursesIds = new Set<number>();
// Store the list of courses.
courses.forEach((course) => {
coursesIds.add(course.id);
// Move category field to categoryid on a course.
course.categoryid = course.category;
delete course.category;
});
this.userCoursesIds = coursesIds;
}
return courses;
}));
});
}
/**
* Get cache key for get user courses WS call.
*
* @return Cache key.
*/
protected getUserCoursesCacheKey(): string {
return ROOT_CACHE_KEY + 'usercourses';
}
/**
* Invalidates get categories WS call.
*
* @param categoryId Category ID to get.
* @param addSubcategories If it should add subcategories to the list.
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateCategories(categoryId: number, addSubcategories?: boolean, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKey(this.getCategoriesCacheKey(categoryId, addSubcategories));
}
/**
* Invalidates get course WS call.
*
* @param id Course ID.
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
invalidateCourse(id: number, siteId?: string): Promise<void> {
return this.invalidateCourses([id], siteId);
}
/**
* Invalidates get course enrolment methods WS call.
*
* @param id Course ID.
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateCourseEnrolmentMethods(id: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKey(this.getCourseEnrolmentMethodsCacheKey(id));
}
/**
* Invalidates get course guest enrolment info WS call.
*
* @param instanceId Guest instance ID.
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateCourseGuestEnrolmentInfo(instanceId: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKey(this.getCourseGuestEnrolmentInfoCacheKey(instanceId));
}
/**
* Invalidates the navigation and administration options for the given courses.
*
* @param courseIds IDs of courses to get.
* @param siteId Site ID to invalidate. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateCoursesAdminAndNavOptions(courseIds: number[], siteId?: string): Promise<void> {
siteId = siteId || CoreSites.getCurrentSiteId();
const ids = await this.getCourseIdsForAdminAndNavOptions(courseIds, siteId);
const promises: Promise<void>[] = [];
promises.push(this.invalidateUserAdministrationOptionsForCourses(ids, siteId));
promises.push(this.invalidateUserNavigationOptionsForCourses(ids, siteId));
await Promise.all(promises);
}
/**
* Invalidates get courses WS call.
*
* @param ids Courses IDs.
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateCourses(ids: number[], siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKey(this.getCoursesCacheKey(ids));
}
/**
* Invalidates get courses by field WS call.
*
* @param field See getCoursesByField for info.
* @param value The value to match.
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateCoursesByField(field: string = '', value: number | string = '', siteId?: string): Promise<void> {
if (typeof value === 'string' && value.length === 0) {
return;
}
siteId = siteId || CoreSites.getCurrentSiteId();
const result = await this.fixCoursesByFieldParams(field, value, siteId);
field = result.field;
value = result.value;
const site = await CoreSites.getSite(siteId);
return site.invalidateWsCacheForKey(this.getCoursesByFieldCacheKey(field, value));
}
/**
* Invalidates get recent courses WS call.
*
* @param userId User ID. If not defined, current user.
* @param siteId Site Id. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateRecentCourses(userId?: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKey(this.getRecentCoursesCacheKey(userId || site.getUserId()));
}
/**
* Invalidates all user administration options.
*
* @param siteId Site ID to invalidate. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateUserAdministrationOptions(siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKeyStartingWith(this.getUserAdministrationOptionsCommonCacheKey());
}
/**
* Invalidates user administration options for certain courses.
*
* @param courseIds IDs of courses.
* @param siteId Site ID to invalidate. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateUserAdministrationOptionsForCourses(courseIds: number[], siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKey(this.getUserAdministrationOptionsCacheKey(courseIds));
}
/**
* Invalidates get user courses WS call.
*
* @param siteId Site ID to invalidate. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateUserCourses(siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKey(this.getUserCoursesCacheKey());
}
/**
* Invalidates all user navigation options.
*
* @param siteId Site ID to invalidate. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateUserNavigationOptions(siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKeyStartingWith(this.getUserNavigationOptionsCommonCacheKey());
}
/**
* Invalidates user navigation options for certain courses.
*
* @param courseIds IDs of courses.
* @param siteId Site ID to invalidate. If not defined, use current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateUserNavigationOptionsForCourses(courseIds: number[], siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKey(this.getUserNavigationOptionsCacheKey(courseIds));
}
/**
* Check if WS to retrieve guest enrolment data is available.
*
* @return Whether guest WS is available.
* @deprecated since app 3.9.5
*/
isGuestWSAvailable(): boolean {
return true;
}
/**
* Search courses.
*
* @param text Text to search.
* @param page Page to get.
* @param perPage Number of courses per page. Defaults to CoreCoursesProvider.SEARCH_PER_PAGE.
* @param limitToEnrolled Limit to enrolled courses.
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved with the courses and the total of matches.
*/
async search(
text: string,
page: number = 0,
perPage: number = CoreCoursesProvider.SEARCH_PER_PAGE,
limitToEnrolled: boolean = false,
siteId?: string,
): Promise<{ total: number; courses: CoreCourseBasicSearchedData[] }> {
const site = await CoreSites.getSite(siteId);
const params: CoreCourseSearchCoursesWSParams = {
criterianame: 'search',
criteriavalue: text,
page: page,
perpage: perPage,
limittoenrolled: limitToEnrolled,
};
const preSets: CoreSiteWSPreSets = {
getFromCache: false,
};
const response = await site.read<CoreCourseSearchCoursesWSResponse>('core_course_search_courses', params, preSets);
return ({ total: response.total, courses: response.courses });
}
/**
* Self enrol current user in a certain course.
*
* @param courseId Course ID.
* @param password Password to use.
* @param instanceId Enrol instance ID.
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved if the user is enrolled. If the password is invalid, the promise is rejected
* with an object with errorcode = CoreCoursesProvider.ENROL_INVALID_KEY.
*/
async selfEnrol(courseId: number, password: string = '', instanceId?: number, siteId?: string): Promise<boolean> {
const site = await CoreSites.getSite(siteId);
const params: EnrolSelfEnrolUserWSParams = {
courseid: courseId,
password: password,
};
if (instanceId) {
params.instanceid = instanceId;
}
const response = await site.write<CoreStatusWithWarningsWSResponse>('enrol_self_enrol_user', params);
if (!response) {
throw Error('WS enrol_self_enrol_user failed');
}
if (response.status) {
return true;
}
if (response.warnings && response.warnings.length) {
// Invalid password warnings.
const warning = response.warnings.find((warning) =>
warning.warningcode == '2' || warning.warningcode == '3' || warning.warningcode == '4');
if (warning) {
throw new CoreWSError({ errorcode: CoreCoursesProvider.ENROL_INVALID_KEY, message: warning.message });
} else {
throw new CoreWSError(response.warnings[0]);
}
}
throw Error('WS enrol_self_enrol_user failed without warnings');
}
/**
* Set favourite property on a course.
*
* @param courseId Course ID.
* @param favourite If favourite or unfavourite.
* @param siteId Site ID. If not defined, use current site.
* @return Promise resolved when done.
*/
async setFavouriteCourse(courseId: number, favourite: boolean, siteId?: string): Promise<CoreWarningsWSResponse> {
const site = await CoreSites.getSite(siteId);
const params: CoreCourseSetFavouriteCoursesWSParams = {
courses: [
{
id: courseId,
favourite: favourite,
},
],
};
return site.write('core_course_set_favourite_courses', params);
}
/**
* Get download options enabled option.
*
* @return True if enabled, false otherwise.
*/
getCourseDownloadOptionsEnabled(): boolean {
return this.downloadOptionsEnabled;
}
/**
* Set trigger and save the download option.
*
* @param enable True to enable, false to disable.
*/
setCourseDownloadOptionsEnabled(enable: boolean): void {
if (this.downloadOptionsEnabled == enable) {
return;
}
this.downloadOptionsEnabled = enable;
CoreEvents.trigger(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED, { enabled: enable });
}
}
export const CoreCourses = makeSingleton(CoreCoursesProvider);
/**
* Data sent to the EVENT_MY_COURSES_UPDATED.
*/
export type CoreCoursesMyCoursesUpdatedEventData = {
action: string; // Action performed.
courseId?: number; // Course ID affected (if any).
course?: CoreCourseAnyCourseData; // Course affected (if any).
state?: string; // Only for ACTION_STATE_CHANGED. The state that changed (hidden, favourite).
value?: boolean; // The new value for the state changed.
};
/**
* Data sent to the EVENT_MY_COURSES_CHANGED.
*/
export type CoreCoursesMyCoursesChangedEventData = {
added: number[];
removed: number[];
};
/**
* Data sent to the EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED.
*/
export type CoreCoursesDashboardDownloadEnabledChangedEventData = {
enabled: boolean;
};
/**
* Params of core_enrol_get_users_courses WS.
*/
type CoreEnrolGetUsersCoursesWSParams = {
userid: number; // User id.
returnusercount?: boolean; // Include count of enrolled users for each course? This can add several seconds to the response
// time if a user is on several large courses, so set this to false if the value will not be used to improve performance.
};
/**
* Data returned by core_enrol_get_users_courses WS.
*/
type CoreEnrolGetUsersCoursesWSResponse = (CoreEnrolledCourseData & {
category?: number; // Course category id.
})[];
/**
* Basic data obtained form any course.
*/
export type CoreCourseBasicData = {
id: number; // Course id.
fullname: string; // Course full name.
displayname?: string; // Course display name.
shortname: string; // Course short name.
summary: string; // Summary.
summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
categoryid?: number; // Course category id.
};
/**
* Basic data obtained from a course when the user is enrolled.
*/
export type CoreEnrolledCourseBasicData = CoreCourseBasicData & {
idnumber?: string; // Id number of course.
visible?: number; // 1 means visible, 0 means not yet visible course.
format?: string; // Course format: weeks, topics, social, site.
showgrades?: boolean; // True if grades are shown, otherwise false.
lang?: string; // Forced course language.
enablecompletion?: boolean; // True if completion is enabled, otherwise false.
startdate?: number; // Timestamp when the course start.
enddate?: number; // Timestamp when the course end.
};
/**
* Course Data model received when the user is enrolled.
*/
export type CoreEnrolledCourseData = CoreEnrolledCourseBasicData & {
enrolledusercount?: number; // Number of enrolled users in this course.
completionhascriteria?: boolean; // If completion criteria is set.
completionusertracked?: boolean; // If the user is completion tracked.
progress?: number | null; // Progress percentage.
completed?: boolean; // @since 3.6. Whether the course is completed.
marker?: number; // @since 3.6. Course section marker.
lastaccess?: number; // @since 3.6. Last access to the course (timestamp).
isfavourite?: boolean; // If the user marked this course a favourite.
hidden?: boolean; // If the user hide the course from the dashboard.
overviewfiles?: CoreWSExternalFile[]; // @since 3.6.
showactivitydates?: boolean; // @since 3.11. Whether the activity dates are shown or not.
showcompletionconditions?: boolean; // @since 3.11. Whether the activity completion conditions are shown or not.
timemodified?: number; // @since 4.0. Last time course settings were updated (timestamp).
};
/**
* Basic course data received on search.
*/
export type CoreCourseBasicSearchedData = CoreCourseBasicData & {
categoryid: number; // Category id.
categoryname: string; // Category name.
sortorder?: number; // Sort order in the category.
summaryfiles?: CoreWSExternalFile[];
overviewfiles: CoreWSExternalFile[];
contacts: { // Contact users.
id: number; // Contact user id.
fullname: string; // Contact user fullname.
}[];
enrollmentmethods: string[]; // Enrollment methods list.
customfields?: CoreCourseCustomField[]; // Custom fields and associated values.
showactivitydates?: boolean; // @since 3.11. Whether the activity dates are shown or not.
showcompletionconditions?: boolean; // @since 3.11. Whether the activity completion conditions are shown or not.
};
export type CoreCourseSearchedData = CoreCourseBasicSearchedData & {
idnumber?: string; // Id number.
format?: string; // Course format: weeks, topics, social, site,..
showgrades?: number; // 1 if grades are shown, otherwise 0.
newsitems?: number; // Number of recent items appearing on the course page.
startdate?: number; // Timestamp when the course start.
enddate?: number; // Timestamp when the course end.
maxbytes?: number; // Largest size of file that can be uploaded into.
showreports?: number; // Are activity report shown (yes = 1, no =0).
visible?: number; // 1: available to student, 0:not available.
groupmode?: number; // No group, separate, visible.
groupmodeforce?: number; // 1: yes, 0: no.
defaultgroupingid?: number; // Default grouping id.
enablecompletion?: number; // Completion enabled? 1: yes 0: no.
completionnotify?: number; // 1: yes 0: no.
lang?: string; // Forced course language.
theme?: string; // Fame of the forced theme.
marker?: number; // Current course marker.
legacyfiles?: number; // If legacy files are enabled.
calendartype?: string; // Calendar type.
timecreated?: number; // Time when the course was created.
timemodified?: number; // Last time the course was updated.
requested?: number; // If is a requested course.
cacherev?: number; // Cache revision number.
filters?: { // Course filters.
filter: string; // Filter plugin name.
localstate: number; // Filter state: 1 for on, -1 for off, 0 if inherit.
inheritedstate: number; // 1 or 0 to use when localstate is set to inherit.
}[];
courseformatoptions?: CoreCourseFormatOption[]; // Additional options for particular course format.
};
/**
* Course to render as list item.
*/
export type CoreCourseListItem = ((CoreCourseSearchedData & CoreCourseWithImageAndColor) |
CoreCourseAnyCourseDataWithExtraInfoAndOptions) & {
isfavourite?: boolean; // If the user marked this course a favourite.
hidden?: boolean; // If the user hide the course from the dashboard.
completionusertracked?: boolean; // If the user is completion tracked.
progress?: number | null; // Progress percentage.
};
export type CoreCourseGetCoursesData = CoreEnrolledCourseBasicData & {
categoryid: number; // Category id.
categorysortorder?: number; // Sort order into the category.
newsitems?: number; // Number of recent items appearing on the course page.
/**
* Number of weeks/topics.
*
* @deprecated use courseformatoptions.
*/
numsections?: number;
maxbytes?: number; // Largest size of file that can be uploaded into the course.
showreports?: number; // Are activity report shown (yes = 1, no =0).
/**
* How the hidden sections in the course are displayed to students.
*
* @deprecated use courseformatoptions.
*/
hiddensections?: number;
groupmode?: number; // No group, separate, visible.
groupmodeforce?: number; // 1: yes, 0: no.
defaultgroupingid?: number; // Default grouping id.
timecreated?: number; // Timestamp when the course have been created.
timemodified?: number; // Timestamp when the course have been modified.
completionnotify?: number; // 1: yes 0: no.
forcetheme?: string; // Name of the force theme.
courseformatoptions?: CoreCourseFormatOption[]; // Additional options for particular course format.
customfields?: CoreCourseCustomField[]; // Custom fields and associated values.
showactivitydates?: boolean; // @since 3.11. Whether the activity dates are shown or not.
showcompletionconditions?: boolean; // @since 3.11. Whether the activity completion conditions are shown or not.
};
/**
* Course custom fields and associated values.
*/
export type CoreCourseCustomField = {
name: string; // The name of the custom field.
shortname: string; // The shortname of the custom field.
type: string; // The type of the custom field - text, checkbox...
valueraw: string; // The raw value of the custom field.
value: string; // The value of the custom field.
};
/**
* Additional options for particular course format.
*/
export type CoreCourseFormatOption = {
name: string; // Course format option name.
value: string; // Course format option value.
};
/**
* Indexed course format options.
*/
export type CoreCourseFormatOptionsIndexed = {
[name: string]: string;
};
/**
* Params of core_course_get_courses_by_field WS.
*/
type CoreCourseGetCoursesByFieldWSParams = {
/**
* The field to search can be left empty for all courses or:
* id: course id
* ids: comma separated course ids
* shortname: course short name
* idnumber: course id number
* category: category id the course belongs to.
*/
field?: string;
value?: string | number; // The value to match.
};
/**
* Data returned by core_course_get_courses_by_field WS.
*/
export type CoreCourseGetCoursesByFieldWSResponse = {
courses: CoreCourseSearchedData[];
warnings?: CoreWSExternalWarning[];
};
/**
* Params of core_course_search_courses WS.
*/
type CoreCourseSearchCoursesWSParams = {
criterianame: string; // Criteria name (search, modulelist (only admins), blocklist (only admins), tagid).
criteriavalue: string; // Criteria value.
page?: number; // Page number (0 based).
perpage?: number; // Items per page.
requiredcapabilities?: string[]; // Optional list of required capabilities (used to filter the list).
limittoenrolled?: boolean; // Limit to enrolled courses.
onlywithcompletion?: boolean; // Limit to courses where completion is enabled.
};
/**
* Data returned by core_course_search_courses WS.
*/
export type CoreCourseSearchCoursesWSResponse = {
total: number; // Total course count.
courses: CoreCourseBasicSearchedData[];
warnings?: CoreWSExternalWarning[];
};
/**
* Params of core_course_get_courses WS.
*/
type CoreCourseGetCoursesWSParams = {
options?: {
ids?: number[]; // List of course id. If empty return all courses except front page course.
}; // Options - operator OR is used.
};
/**
* Data returned by core_course_get_courses WS.
*/
export type CoreCourseGetCoursesWSResponse = CoreCourseGetCoursesData[];
/**
* Course data exported by course_summary_exporter;
*/
export type CoreCourseSummaryData = CoreCourseBasicData & { // Course.
idnumber: string; // Idnumber.
startdate: number; // Startdate.
enddate: number; // Enddate.
visible: boolean; // Visible.
showactivitydates: boolean; // Showactivitydates.
showcompletionconditions: boolean; // Showcompletionconditions.
fullnamedisplay: string; // Fullnamedisplay.
viewurl: string; // Viewurl.
courseimage: string; // Courseimage.
progress?: number; // Progress.
hasprogress: boolean; // Hasprogress.
isfavourite: boolean; // Isfavourite.
hidden: boolean; // Hidden.
timeaccess?: number; // Timeaccess.
showshortname: boolean; // Showshortname.
coursecategory: string; // Coursecategory.
};
/**
* Params of core_course_get_enrolled_courses_by_timeline_classification WS.
*/
type CoreCourseGetEnrolledCoursesByTimelineClassificationWSParams = {
classification: string; // Future, inprogress, or past.
limit?: number; // Result set limit.
offset?: number; // Result set offset.
sort?: string; // Sort string.
customfieldname?: string; // Used when classification = customfield.
customfieldvalue?: string; // Used when classification = customfield.
};
/**
* Data returned by core_course_get_enrolled_courses_by_timeline_classification WS.
*/
export type CoreCourseGetEnrolledCoursesByTimelineClassificationWSResponse = {
courses: CoreCourseSummaryData[];
nextoffset: number; // Offset for the next request.
};
/**
* Params of core_course_get_categories WS.
*/
type CoreCourseGetCategoriesWSParams = {
criteria?: { // Criteria.
/**
* The category column to search, expected keys (value format) are:
* "id" (int) the category id,
* "ids" (string) category ids separated by commas,
* "name" (string) the category name,
* "parent" (int) the parent category id,
* "idnumber" (string) category idnumber - user must have 'moodle/category:manage' to search on idnumber,
* "visible" (int) whether the returned categories must be visible or hidden.
* If the key is not passed, then the function return all categories that the user can see..
*/
key: string;
value: string | number; // The value to match.
}[];
addsubcategories?: boolean; // Return the sub categories infos (1 - default) otherwise only the category info (0).
};
/**
* Data returned by core_course_get_categories WS.
*/
export type CoreCourseGetCategoriesWSResponse = CoreCategoryData[];
/**
* Category data model.
*/
export type CoreCategoryData = {
id: number; // Category id.
name: string; // Category name.
idnumber?: string; // Category id number.
description: string; // Category description.
descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
parent: number; // Parent category id.
sortorder: number; // Category sorting order.
coursecount: number; // Number of courses in this category.
visible?: number; // 1: available, 0:not available.
visibleold?: number; // 1: available, 0:not available.
timemodified?: number; // Timestamp.
depth: number; // Category depth.
path: string; // Category path.
theme?: string; // Category theme.
};
/**
* Params of core_course_get_user_navigation_options and core_course_get_user_administration_options WS.
*/
type CoreCourseGetUserAdminOrNavOptionsWSParams = {
courseids: number[];
};
/**
* Data returned by core_course_get_user_navigation_options and core_course_get_user_administration_options WS.
*/
export type CoreCourseGetUserAdminOrNavOptionsWSResponse = {
courses: CoreCourseUserAdminOrNavOption[];
warnings?: CoreWSExternalWarning[];
};
/**
* Admin or navigation option data.
*/
export type CoreCourseUserAdminOrNavOption = {
id: number; // Course id.
options: {
name: string; // Option name.
available: boolean; // Whether the option is available or not.
}[];
};
/**
* Indexed administration or navigation course options.
*/
export type CoreCourseUserAdminOrNavOptionCourseIndexed = {
[id: number]: CoreCourseUserAdminOrNavOptionIndexed;
};
/**
* Indexed administration or navigation options.
*/
export type CoreCourseUserAdminOrNavOptionIndexed = {
[name: string]: // Option name.
boolean; // Whether the option is available or not.
};
/**
* Params of core_enrol_get_course_enrolment_methods WS.
*/
type CoreEnrolGetCourseEnrolmentMethodsWSParams = {
courseid: number; // Course id.
};
/**
* Course enrolment method.
*/
export type CoreCourseEnrolmentMethod = {
id: number; // Id of course enrolment instance.
courseid: number; // Id of course.
type: string; // Type of enrolment plugin.
name: string; // Name of enrolment plugin.
status: string; // Status of enrolment plugin.
wsfunction?: string; // Webservice function to get more information.
};
/**
* Params of enrol_guest_get_instance_info WS.
*/
type EnrolGuestGetInstanceInfoWSParams = {
instanceid: number; // Instance id of guest enrolment plugin.
};
/**
* Data returned by enrol_guest_get_instance_info WS.
*/
export type EnrolGuestGetInstanceInfoWSResponse = {
instanceinfo: CoreCourseEnrolmentGuestMethod;
warnings?: CoreWSExternalWarning[];
};
/**
* Params of core_course_get_recent_courses WS.
*/
export type CoreCourseGetRecentCoursesWSParams = {
userid?: number; // Id of the user, default to current user.
limit?: number; // Result set limit.
offset?: number; // Result set offset.
sort?: string; // Sort string.
};
/**
* Options for getRecentCourses.
*/
export type CoreCourseGetRecentCoursesOptions = CoreSitesCommonWSOptions & {
userId?: number; // Id of the user, default to current user.
limit?: number; // Result set limit.
offset?: number; // Result set offset.
sort?: string; // Sort string.
};
/**
* Course guest enrolment method.
*/
export type CoreCourseEnrolmentGuestMethod = CoreCourseEnrolmentMethod & {
passwordrequired: boolean; // Is a password required?.
};
/**
* Params of enrol_self_enrol_user WS.
*/
type EnrolSelfEnrolUserWSParams = {
courseid: number; // Id of the course.
password?: string; // Enrolment key.
instanceid?: number; // Instance id of self enrolment plugin.
};
/**
* Params of core_course_set_favourite_courses WS.
*/
type CoreCourseSetFavouriteCoursesWSParams = {
courses: {
id: number; // Course ID.
favourite: boolean; // Favourite status.
}[];
};
/**
* Any of the possible course data.
*/
export type CoreCourseAnyCourseData = CoreEnrolledCourseData | CoreCourseSearchedData | CoreCourseGetCoursesData;
/**
* Course data with admin and navigation option availability.
*/
export type CoreCourseAnyCourseDataWithOptions = CoreCourseAnyCourseData & {
navOptions?: CoreCourseUserAdminOrNavOptionIndexed;
admOptions?: CoreCourseUserAdminOrNavOptionIndexed;
};