2022-06-08 13:28:05 +02:00

1750 lines
62 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 { CoreError } from '@classes/errors/error';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
import { CoreNetwork } from '@services/network';
import { CoreFilepool } from '@services/filepool';
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile, CoreWSExternalWarning, CoreWSStoredFile } from '@services/ws';
import { makeSingleton, Translate } from '@singletons';
import { AddonModFeedbackOffline } from './feedback-offline';
import { AddonModFeedbackAutoSyncData, AddonModFeedbackSyncProvider } from './feedback-sync';
const ROOT_CACHE_KEY = 'AddonModFeedback:';
/**
* Service that provides some features for feedbacks.
*/
@Injectable({ providedIn: 'root' })
export class AddonModFeedbackProvider {
static readonly COMPONENT = 'mmaModFeedback';
static readonly FORM_SUBMITTED = 'addon_mod_feedback_form_submitted';
static readonly LINE_SEP = '|';
static readonly MULTICHOICE_TYPE_SEP = '>>>>>';
static readonly MULTICHOICE_ADJUST_SEP = '<<<<<';
static readonly MULTICHOICE_HIDENOSELECT = 'h';
static readonly MULTICHOICERATED_VALUE_SEP = '####';
static readonly PER_PAGE = 20;
/**
* Check dependency of a question item.
*
* @param items All question items to check dependency.
* @param item Item to check.
* @return Return true if dependency is acomplished and it can be shown. False, otherwise.
*/
protected checkDependencyItem(items: AddonModFeedbackItem[], item: AddonModFeedbackItem): boolean {
const depend = items.find((itemFind) => itemFind.id == item.dependitem);
// Item not found, looks like dependent item has been removed or is in the same or following pages.
if (!depend) {
return true;
}
switch (depend.typ) {
case 'label':
return false;
case 'multichoice':
case 'multichoicerated':
return this.compareDependItemMultichoice(depend, item.dependvalue);
default:
break;
}
return item.dependvalue == depend.rawValue;
}
/**
* Check dependency item of type Multichoice.
*
* @param item Item to check.
* @param dependValue Value to compare.
* @return Return true if dependency is acomplished and it can be shown. False, otherwise.
*/
protected compareDependItemMultichoice(item: AddonModFeedbackItem, dependValue: string): boolean {
const parts = item.presentation.split(AddonModFeedbackProvider.MULTICHOICE_TYPE_SEP) || [];
const subtype = parts.length > 0 && parts[0] ? parts[0] : 'r';
const choicesStr = (parts[1] || '').split(AddonModFeedbackProvider.MULTICHOICE_ADJUST_SEP)[0] || '';
const choices = choicesStr.split(AddonModFeedbackProvider.LINE_SEP) || [];
let values: AddonModFeedbackResponseValue[];
if (subtype === 'c') {
if (item.rawValue === undefined) {
values = [''];
} else {
item.rawValue = '' + item.rawValue;
values = item.rawValue.split(AddonModFeedbackProvider.LINE_SEP);
}
} else {
values = [item.rawValue || ''];
}
for (let index = 0; index < choices.length; index++) {
for (const x in values) {
if (values[x] == index + 1) {
let value = choices[index];
if (item.typ == 'multichoicerated') {
value = value.split(AddonModFeedbackProvider.MULTICHOICERATED_VALUE_SEP)[1] || '';
}
if (value.trim() == dependValue) {
return true;
}
// We can finish checking if only searching on one value and we found it.
if (values.length == 1) {
return false;
}
}
}
}
return false;
}
/**
* Fill values of item questions.
*
* @param feedbackId Feedback ID.
* @param items Item to fill the value.
* @param options Other options.
* @return Resolved with values when done.
*/
protected async fillValues(
feedbackId: number,
items: AddonModFeedbackWSItem[],
options: CoreCourseCommonModWSOptions = {},
): Promise<AddonModFeedbackItem[]> {
const filledItems = <AddonModFeedbackItem[]> items;
try {
const valuesArray = await this.getCurrentValues(feedbackId, options);
const values: Record<number, string> = {};
valuesArray.forEach((value) => {
values[value.item] = value.value;
});
filledItems.forEach((itemData) => {
if (itemData.hasvalue && values[itemData.id] !== undefined) {
itemData.rawValue = values[itemData.id];
}
});
} catch {
// Ignore errors.
}
// Merge with offline data.
const offlineResponses = await CoreUtils.ignoreErrors(
AddonModFeedbackOffline.getFeedbackResponses(feedbackId, options.siteId),
);
if (!offlineResponses) {
return items;
}
const offlineValues: Record<number, AddonModFeedbackResponseValue[]> = {};
// Merge all values into one array.
const offlineValuesArray = offlineResponses.reduce((array, entry) => {
const responses = <OfflineResponsesArray> CoreUtils.objectToArrayOfObjects(entry.responses, 'id', 'value');
return array.concat(responses);
}, <OfflineResponsesArray> []).map((valueEntry) => {
const parts = valueEntry.id.split('_');
const item = (parts[1] || '').replace(/\[.*\]/, ''); // Remove [0] and similar.
return {
...valueEntry,
typ: parts[0],
item: Number(item),
};
});
offlineValuesArray.forEach((value) => {
if (offlineValues[value.item] === undefined) {
offlineValues[value.item] = [];
}
offlineValues[value.item].push(value.value);
});
filledItems.forEach((item) => {
if (!item.hasvalue || offlineValues[item.id] === undefined) {
return;
}
// Treat multichoice checkboxes.
if (item.typ == 'multichoice' && item.presentation.split(AddonModFeedbackProvider.MULTICHOICE_TYPE_SEP)[0] == 'c') {
offlineValues[item.id] = offlineValues[item.id].filter((value) => value > 0);
item.rawValue = offlineValues[item.id].join(AddonModFeedbackProvider.LINE_SEP);
} else {
item.rawValue = offlineValues[item.id][0];
}
});
return filledItems;
}
/**
* Returns all the feedback non respondents users.
*
* @param feedbackId Feedback ID.
* @param options Other options.
* @param previous Only for recurrent use. Object with the previous fetched info.
* @return Promise resolved when the info is retrieved.
*/
async getAllNonRespondents(
feedbackId: number,
options: AddonModFeedbackGroupOptions = {},
previous?: AddonModFeedbackPreviousNonRespondents,
): Promise<AddonModFeedbackAllNonRespondent> {
options.siteId = options.siteId || CoreSites.getCurrentSiteId();
previous = previous || {
page: 0,
users: [],
};
const response = await this.getNonRespondents(feedbackId, {
page: previous.page,
...options, // Include all options.
});
if (previous.users.length < response.total) {
previous.users = previous.users.concat(response.users);
}
if (previous.users.length < response.total) {
// Can load more.
previous.page++;
return this.getAllNonRespondents(feedbackId, options, previous);
}
return {
...previous,
total: response.total,
};
}
/**
* Returns all the feedback user responses.
*
* @param feedbackId Feedback ID.
* @param options Other options.
* @param previous Only for recurrent use. Object with the previous fetched info.
* @return Promise resolved when the info is retrieved.
*/
async getAllResponsesAnalysis(
feedbackId: number,
options: AddonModFeedbackGroupOptions = {},
previous?: AddonModFeedbackPreviousResponsesAnalysis,
): Promise<AddonModFeedbackAllResponsesAnalysis> {
options.siteId = options.siteId || CoreSites.getCurrentSiteId();
previous = previous || {
page: 0,
attempts: [],
anonattempts: [],
};
const responses = await this.getResponsesAnalysis(feedbackId, {
page: previous.page,
...options, // Include all options.
});
if (previous.anonattempts.length < responses.totalanonattempts) {
previous.anonattempts = previous.anonattempts.concat(responses.anonattempts);
}
if (previous.attempts.length < responses.totalattempts) {
previous.attempts = previous.attempts.concat(responses.attempts);
}
if (previous.anonattempts.length < responses.totalanonattempts || previous.attempts.length < responses.totalattempts) {
// Can load more.
previous.page++;
return this.getAllResponsesAnalysis(feedbackId, options, previous);
}
return {
...previous,
totalattempts: responses.totalattempts,
totalanonattempts: responses.totalanonattempts,
};
}
/**
* Get analysis information for a given feedback.
*
* @param feedbackId Feedback ID.
* @param options Other options.
* @return Promise resolved when the feedback is retrieved.
*/
async getAnalysis(
feedbackId: number,
options: AddonModFeedbackGroupOptions = {},
): Promise<AddonModFeedbackGetAnalysisWSResponse> {
const site = await CoreSites.getSite(options.siteId);
const params: AddonModFeedbackGetAnalysisWSParams = {
feedbackid: feedbackId,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getAnalysisDataCacheKey(feedbackId, options.groupId),
component: AddonModFeedbackProvider.COMPONENT,
componentId: options.cmId,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
};
if (options.groupId) {
params.groupid = options.groupId;
}
return site.read('mod_feedback_get_analysis', params, preSets);
}
/**
* Get cache key for feedback analysis data WS calls.
*
* @param feedbackId Feedback ID.
* @param groupId Group ID.
* @return Cache key.
*/
protected getAnalysisDataCacheKey(feedbackId: number, groupId: number = 0): string {
return this.getAnalysisDataPrefixCacheKey(feedbackId) + groupId;
}
/**
* Get prefix cache key for feedback analysis data WS calls.
*
* @param feedbackId Feedback ID.
* @return Cache key.
*/
protected getAnalysisDataPrefixCacheKey(feedbackId: number): string {
return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':analysis:';
}
/**
* Find an attempt in all responses analysis.
*
* @param feedbackId Feedback ID.
* @param attemptId Attempt ID to find.
* @param options Other options.
* @param previous Only for recurrent use. Object with the previous fetched info.
* @return Promise resolved when the info is retrieved.
*/
async getAttempt(
feedbackId: number,
attemptId: number,
options: CoreCourseCommonModWSOptions = {},
previous?: AddonModFeedbackGetAttemptPreviousData,
): Promise<AddonModFeedbackWSAttempt | AddonModFeedbackWSAnonAttempt> {
options.siteId = options.siteId || CoreSites.getCurrentSiteId();
previous = previous || {
page: 0,
attemptsLoaded: 0,
anonAttemptsLoaded: 0,
};
const responses = await this.getResponsesAnalysis(feedbackId, {
page: previous.page,
groupId: 0,
...options, // Include all options.
});
const attempt = responses.attempts.find((attempt) => attemptId == attempt.id);
if (attempt) {
return attempt;
}
const anonAttempt = responses.anonattempts.find((attempt) => attemptId == attempt.id);
if (anonAttempt) {
return anonAttempt;
}
if (previous.anonAttemptsLoaded < responses.totalanonattempts) {
previous.anonAttemptsLoaded += responses.anonattempts.length;
}
if (previous.attemptsLoaded < responses.totalattempts) {
previous.attemptsLoaded += responses.attempts.length;
}
if (previous.anonAttemptsLoaded < responses.totalanonattempts || previous.attemptsLoaded < responses.totalattempts) {
// Can load more. Check there.
previous.page++;
return this.getAttempt(feedbackId, attemptId, options, previous);
}
// Not found and all loaded. Reject.
throw new CoreError('Attempt not found.');
}
/**
* Get prefix cache key for feedback completion data WS calls.
*
* @param feedbackId Feedback ID.
* @return Cache key.
*/
protected getCompletedDataCacheKey(feedbackId: number): string {
return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':completed:';
}
/**
* Returns the temporary completion timemodified for the current user.
*
* @param feedbackId Feedback ID.
* @param options Other options.
* @return Promise resolved when the info is retrieved.
*/
async getCurrentCompletedTimeModified(feedbackId: number, options: CoreCourseCommonModWSOptions = {}): Promise<number> {
const site = await CoreSites.getSite(options.siteId);
const params: AddonModFeedbackGetCurrentCompletedTmpWSParams = {
feedbackid: feedbackId,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getCurrentCompletedTimeModifiedDataCacheKey(feedbackId),
component: AddonModFeedbackProvider.COMPONENT,
componentId: options.cmId,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
};
try {
const response = await site.read<AddonModFeedbackGetCurrentCompletedTmpWSResponse>(
'mod_feedback_get_current_completed_tmp',
params,
preSets,
);
return response.feedback.timemodified;
} catch {
// Ignore errors.
return 0;
}
}
/**
* Get prefix cache key for feedback current completed temp data WS calls.
*
* @param feedbackId Feedback ID.
* @return Cache key.
*/
protected getCurrentCompletedTimeModifiedDataCacheKey(feedbackId: number): string {
return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':completedtime:';
}
/**
* Returns the temporary responses or responses of the last submission for the current user.
*
* @param feedbackId Feedback ID.
* @param options Other options.
* @return Promise resolved when the info is retrieved.
*/
async getCurrentValues(
feedbackId: number,
options: CoreCourseCommonModWSOptions = {},
): Promise<AddonModFeedbackWSResponse[]> {
const site = await CoreSites.getSite(options.siteId);
const params: AddonModFeedbackGetUnfinishedResponsesWSParams = {
feedbackid: feedbackId,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getCurrentValuesDataCacheKey(feedbackId),
component: AddonModFeedbackProvider.COMPONENT,
componentId: options.cmId,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
};
const response = await site.read<AddonModFeedbackGetUnfinishedResponsesWSResponse>(
'mod_feedback_get_unfinished_responses',
params,
preSets,
);
if (response.responses.length) {
return response.responses;
}
// No unfinished responses, fetch responses of the last submission.
const finishedResponse = await site.read<AddonModFeedbackGetFinishedResponsesWSResponse>(
'mod_feedback_get_finished_responses',
params,
preSets,
);
return finishedResponse.responses;
}
/**
* Get cache key for get current values feedback data WS calls.
*
* @param feedbackId Feedback ID.
* @return Cache key.
*/
protected getCurrentValuesDataCacheKey(feedbackId: number): string {
return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':currentvalues';
}
/**
* Get access information for a given feedback.
*
* @param feedbackId Feedback ID.
* @param options Other options.
* @return Promise resolved when the feedback is retrieved.
*/
async getFeedbackAccessInformation(
feedbackId: number,
options: CoreCourseCommonModWSOptions = {},
): Promise<AddonModFeedbackGetFeedbackAccessInformationWSResponse> {
const site = await CoreSites.getSite(options.siteId);
const params: AddonModFeedbackGetFeedbackAccessInformationWSParams = {
feedbackid: feedbackId,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getFeedbackAccessInformationDataCacheKey(feedbackId),
component: AddonModFeedbackProvider.COMPONENT,
componentId: options.cmId,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
};
return site.read('mod_feedback_get_feedback_access_information', params, preSets);
}
/**
* Get cache key for feedback access information data WS calls.
*
* @param feedbackId Feedback ID.
* @return Cache key.
*/
protected getFeedbackAccessInformationDataCacheKey(feedbackId: number): string {
return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':access';
}
/**
* Get cache key for feedback data WS calls.
*
* @param courseId Course ID.
* @return Cache key.
*/
protected getFeedbackCacheKey(courseId: number): string {
return ROOT_CACHE_KEY + 'feedback:' + courseId;
}
/**
* Get prefix cache key for all feedback activity data WS calls.
*
* @param feedbackId Feedback ID.
* @return Cache key.
*/
protected getFeedbackDataPrefixCacheKey(feedbackId: number): string {
return ROOT_CACHE_KEY + feedbackId;
}
/**
* Get a feedback with key=value. If more than one is found, only the first will be returned.
*
* @param courseId Course ID.
* @param key Name of the property to check.
* @param value Value to search.
* @param options Other options.
* @return Promise resolved when the feedback is retrieved.
*/
protected async getFeedbackDataByKey(
courseId: number,
key: string,
value: unknown,
options: CoreSitesCommonWSOptions = {},
): Promise<AddonModFeedbackWSFeedback> {
const site = await CoreSites.getSite(options.siteId);
const params: AddonModFeedbackGetFeedbacksByCoursesWSParams = {
courseids: [courseId],
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getFeedbackCacheKey(courseId),
updateFrequency: CoreSite.FREQUENCY_RARELY,
component: AddonModFeedbackProvider.COMPONENT,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
};
const response = await site.read<AddonModFeedbackGetFeedbacksByCoursesWSResponse>(
'mod_feedback_get_feedbacks_by_courses',
params,
preSets,
);
const currentFeedback = response.feedbacks.find((feedback) => feedback[key] == value);
if (currentFeedback) {
return currentFeedback;
}
throw new CoreError(Translate.instant('core.course.modulenotfound'));
}
/**
* Get a feedback by course module ID.
*
* @param courseId Course ID.
* @param cmId Course module ID.
* @param options Other options.
* @return Promise resolved when the feedback is retrieved.
*/
getFeedback(courseId: number, cmId: number, options: CoreSitesCommonWSOptions = {}): Promise<AddonModFeedbackWSFeedback> {
return this.getFeedbackDataByKey(courseId, 'coursemodule', cmId, options);
}
/**
* Get a feedback by ID.
*
* @param courseId Course ID.
* @param id Feedback ID.
* @param options Other options.
* @return Promise resolved when the feedback is retrieved.
*/
getFeedbackById(courseId: number, id: number, options: CoreSitesCommonWSOptions = {}): Promise<AddonModFeedbackWSFeedback> {
return this.getFeedbackDataByKey(courseId, 'id', id, options);
}
/**
* Returns the items (questions) in the given feedback.
*
* @param feedbackId Feedback ID.
* @param options Other options.
* @return Promise resolved when the info is retrieved.
*/
async getItems(feedbackId: number, options: CoreCourseCommonModWSOptions = {}): Promise<AddonModFeedbackGetItemsWSResponse> {
const site = await CoreSites.getSite(options.siteId);
const params: AddonModFeedbackGetItemsWSParams = {
feedbackid: feedbackId,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getItemsDataCacheKey(feedbackId),
updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
component: AddonModFeedbackProvider.COMPONENT,
componentId: options.cmId,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
};
return site.read('mod_feedback_get_items', params, preSets);
}
/**
* Get cache key for get items feedback data WS calls.
*
* @param feedbackId Feedback ID.
* @return Cache key.
*/
protected getItemsDataCacheKey(feedbackId: number): string {
return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':items';
}
/**
* Retrieves a list of students who didn't submit the feedback.
*
* @param feedbackId Feedback ID.
* @param options Other options.
* @return Promise resolved when the info is retrieved.
*/
async getNonRespondents(
feedbackId: number,
options: AddonModFeedbackGroupPaginatedOptions = {},
): Promise<AddonModFeedbackGetNonRespondentsWSResponse> {
options.groupId = options.groupId || 0;
options.page = options.page || 0;
const site = await CoreSites.getSite(options.siteId);
const params: AddonModFeedbackGetNonRespondentsWSParams = {
feedbackid: feedbackId,
groupid: options.groupId,
page: options.page,
perpage: AddonModFeedbackProvider.PER_PAGE,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getNonRespondentsDataCacheKey(feedbackId, options.groupId),
component: AddonModFeedbackProvider.COMPONENT,
componentId: options.cmId,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
};
return site.read('mod_feedback_get_non_respondents', params, preSets);
}
/**
* Get cache key for non respondents feedback data WS calls.
*
* @param feedbackId Feedback ID.
* @param groupId Group id, 0 means that the function will determine the user group.
* @return Cache key.
*/
protected getNonRespondentsDataCacheKey(feedbackId: number, groupId: number = 0): string {
return this.getNonRespondentsDataPrefixCacheKey(feedbackId) + groupId;
}
/**
* Get prefix cache key for feedback non respondents data WS calls.
*
* @param feedbackId Feedback ID.
* @return Cache key.
*/
protected getNonRespondentsDataPrefixCacheKey(feedbackId: number): string {
return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':nonrespondents:';
}
/**
* Get a single feedback page items. This function is not cached, use AddonModFeedbackHelperProvider#getPageItems instead.
*
* @param feedbackId Feedback ID.
* @param page The page to get.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the info is retrieved.
*/
async getPageItems(feedbackId: number, page: number, siteId?: string): Promise<AddonModFeedbackGetPageItemsWSResponse> {
const site = await CoreSites.getSite(siteId);
const params: AddonModFeedbackGetPageItemsWSParams = {
feedbackid: feedbackId,
page: page,
};
return site.write('mod_feedback_get_page_items', params);
}
/**
* Get a single feedback page items. If offline or server down it will use getItems to calculate dependencies.
*
* @param feedbackId Feedback ID.
* @param page The page to get.
* @param options Other options.
* @return Promise resolved when the info is retrieved.
*/
async getPageItemsWithValues(
feedbackId: number,
page: number,
options: CoreCourseCommonModWSOptions = {},
): Promise<AddonModFeedbackPageItems> {
options.siteId = options.siteId || CoreSites.getCurrentSiteId();
try {
const response: AddonModFeedbackPageItems = await this.getPageItems(feedbackId, page, options.siteId);
response.items = await this.fillValues(feedbackId, response.items, options);
return response;
} catch {
// If getPageItems fail we should calculate it using getItems.
const response = await this.getItems(feedbackId, options);
const items = await this.fillValues(feedbackId, response.items, options);
// Separate items by pages.
let currentPage = 0;
const previousPageItems: AddonModFeedbackItem[] = [];
const pageItems = items.filter((item) => {
// Greater page, discard all entries.
if (currentPage > page) {
return false;
}
if (item.typ == 'pagebreak') {
currentPage++;
return false;
}
// Save items on previous page to check dependencies and discard entry.
if (currentPage < page) {
previousPageItems.push(item);
return false;
}
// Filter depending items.
if (item && item.dependitem > 0 && previousPageItems.length > 0) {
return this.checkDependencyItem(previousPageItems, item);
}
// Filter items with errors.
return item;
});
return {
items: pageItems,
hasprevpage: page > 0,
hasnextpage: currentPage > page,
warnings: response.warnings,
};
}
}
/**
* Convenience function to get the page we can jump.
*
* @param feedbackId Feedback ID.
* @param page Page where we want to jump.
* @param changePage If page change is forward (1) or backward (-1).
* @param options Other options.
* @return Page number where to jump. Or false if completed or first page.
*/
protected async getPageJumpTo(
feedbackId: number,
page: number,
changePage: number,
options: { cmId?: number; siteId?: string },
): Promise<number | false> {
const response = await this.getPageItemsWithValues(feedbackId, page, {
cmId: options.cmId,
readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE,
siteId: options.siteId,
});
// The page we are going has items.
if (response.items.length > 0) {
return page;
}
// Check we can jump futher.
if ((changePage == 1 && response.hasnextpage) || (changePage == -1 && response.hasprevpage)) {
return this.getPageJumpTo(feedbackId, page + changePage, changePage, options);
}
// Completed or first page.
return false;
}
/**
* Returns the feedback user responses.
*
* @param feedbackId Feedback ID.
* @param options Other options.
* @return Promise resolved when the info is retrieved.
*/
async getResponsesAnalysis(
feedbackId: number,
options: AddonModFeedbackGroupPaginatedOptions = {},
): Promise<AddonModFeedbackGetResponsesAnalysisWSResponse> {
options.groupId = options.groupId || 0;
options.page = options.page || 0;
const site = await CoreSites.getSite(options.siteId);
const params: AddonModFeedbackGetResponsesAnalysisWSParams = {
feedbackid: feedbackId,
groupid: options.groupId,
page: options.page,
perpage: AddonModFeedbackProvider.PER_PAGE,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getResponsesAnalysisDataCacheKey(feedbackId, options.groupId),
component: AddonModFeedbackProvider.COMPONENT,
componentId: options.cmId,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
};
return site.read('mod_feedback_get_responses_analysis', params, preSets);
}
/**
* Get cache key for responses analysis feedback data WS calls.
*
* @param feedbackId Feedback ID.
* @param groupId Group id, 0 means that the function will determine the user group.
* @return Cache key.
*/
protected getResponsesAnalysisDataCacheKey(feedbackId: number, groupId: number = 0): string {
return this.getResponsesAnalysisDataPrefixCacheKey(feedbackId) + groupId;
}
/**
* Get prefix cache key for feedback responses analysis data WS calls.
*
* @param feedbackId Feedback ID.
* @return Cache key.
*/
protected getResponsesAnalysisDataPrefixCacheKey(feedbackId: number): string {
return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':responsesanalysis:';
}
/**
* Gets the resume page information.
*
* @param feedbackId Feedback ID.
* @param options Other options.
* @return Promise resolved when the info is retrieved.
*/
async getResumePage(feedbackId: number, options: CoreCourseCommonModWSOptions = {}): Promise<number> {
const site = await CoreSites.getSite(options.siteId);
const params: AddonModFeedbackLaunchFeedbackWSParams = {
feedbackid: feedbackId,
};
const preSets = {
cacheKey: this.getResumePageDataCacheKey(feedbackId),
component: AddonModFeedbackProvider.COMPONENT,
componentId: options.cmId,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
};
const response = await site.read<AddonModFeedbackLaunchFeedbackWSResponse>('mod_feedback_launch_feedback', params, preSets);
// WS will return -1 for last page but the user need to start again.
return response.gopage > 0 ? response.gopage : 0;
}
/**
* Get prefix cache key for resume feedback page data WS calls.
*
* @param feedbackId Feedback ID.
* @return Cache key.
*/
protected getResumePageDataCacheKey(feedbackId: number): string {
return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':launch';
}
/**
* Invalidates feedback data except files and module info.
*
* @param feedbackId Feedback ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateAllFeedbackData(feedbackId: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKeyStartingWith(this.getFeedbackDataPrefixCacheKey(feedbackId));
}
/**
* Invalidates feedback analysis data.
*
* @param feedbackId Feedback ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateAnalysisData(feedbackId: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
return site.invalidateWsCacheForKeyStartingWith(this.getAnalysisDataPrefixCacheKey(feedbackId));
}
/**
* Invalidate the prefetched content.
* To invalidate files, use AddonModFeedbackProvider#invalidateFiles.
*
* @param moduleId The module ID.
* @param courseId Course ID of the module.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise<void> {
siteId = siteId || CoreSites.getCurrentSiteId();
const feedback = await this.getFeedback(courseId, moduleId, { siteId });
await Promise.all([
this.invalidateFeedbackData(courseId, siteId),
this.invalidateAllFeedbackData(feedback.id, siteId),
]);
}
/**
* Invalidates temporary completion record data.
*
* @param feedbackId Feedback ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateCurrentValuesData(feedbackId: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
return site.invalidateWsCacheForKey(this.getCurrentValuesDataCacheKey(feedbackId));
}
/**
* Invalidates feedback access information data.
*
* @param feedbackId Feedback ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateFeedbackAccessInformationData(feedbackId: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
return site.invalidateWsCacheForKey(this.getFeedbackAccessInformationDataCacheKey(feedbackId));
}
/**
* Invalidates feedback data.
*
* @param courseId Course ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateFeedbackData(courseId: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
return site.invalidateWsCacheForKey(this.getFeedbackCacheKey(courseId));
}
/**
* Invalidate the prefetched files.
*
* @param moduleId The module ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the files are invalidated.
*/
async invalidateFiles(moduleId: number, siteId?: string): Promise<void> {
return CoreFilepool.invalidateFilesByComponent(siteId, AddonModFeedbackProvider.COMPONENT, moduleId);
}
/**
* Invalidates feedback non respondents record data.
*
* @param feedbackId Feedback ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateNonRespondentsData(feedbackId: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKeyStartingWith(this.getNonRespondentsDataPrefixCacheKey(feedbackId));
}
/**
* Invalidates feedback user responses record data.
*
* @param feedbackId Feedback ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateResponsesAnalysisData(feedbackId: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKeyStartingWith(this.getResponsesAnalysisDataPrefixCacheKey(feedbackId));
}
/**
* Invalidates launch feedback data.
*
* @param feedbackId Feedback ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateResumePageData(feedbackId: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
await site.invalidateWsCacheForKey(this.getResumePageDataCacheKey(feedbackId));
}
/**
* Returns if feedback has been completed
*
* @param feedbackId Feedback ID.
* @param options Other options.
* @return Promise resolved when the info is retrieved.
*/
async isCompleted(feedbackId: number, options: CoreCourseCommonModWSOptions = {}): Promise<boolean> {
const site = await CoreSites.getSite(options.siteId);
const params: AddonModFeedbackGetLastCompletedWSParams = {
feedbackid: feedbackId,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getCompletedDataCacheKey(feedbackId),
updateFrequency: CoreSite.FREQUENCY_RARELY,
component: AddonModFeedbackProvider.COMPONENT,
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
};
return CoreUtils.promiseWorks(site.read('mod_feedback_get_last_completed', params, preSets));
}
/**
* Report the feedback as being viewed.
*
* @param id Module ID.
* @param name Name of the feedback.
* @param formViewed True if form was viewed.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the WS call is successful.
*/
async logView(id: number, name?: string, formViewed: boolean = false, siteId?: string): Promise<void> {
const params: AddonModFeedbackViewFeedbackWSParams = {
feedbackid: id,
moduleviewed: formViewed,
};
await CoreCourseLogHelper.logSingle(
'mod_feedback_view_feedback',
params,
AddonModFeedbackProvider.COMPONENT,
id,
name,
'feedback',
{ moduleviewed: params.moduleviewed },
siteId,
);
}
/**
* Process a jump between pages.
*
* @param feedbackId Feedback ID.
* @param page The page being processed.
* @param responses The data to be processed the key is the field name (usually type[index]_id).
* @param options Other options.
* @return Promise resolved when the info is retrieved.
*/
async processPage(
feedbackId: number,
page: number,
responses: Record<string, AddonModFeedbackResponseValue>,
options: AddonModFeedbackProcessPageOptions = {},
): Promise<AddonModFeedbackProcessPageResponse> {
options.siteId = options.siteId || CoreSites.getCurrentSiteId();
// Convenience function to store a message to be synchronized later.
const storeOffline = async (): Promise<AddonModFeedbackProcessPageResponse> => {
await AddonModFeedbackOffline.saveResponses(feedbackId, page, responses, options.courseId!, options.siteId);
// Simulate process_page response.
const response: AddonModFeedbackProcessPageResponse = {
jumpto: page,
completed: false,
offline: true,
};
let changePage = 0;
if (options.goPrevious) {
if (page > 0) {
changePage = -1;
}
} else if (!options.formHasErrors) {
// We can only go next if it has no errors.
changePage = 1;
}
if (changePage === 0) {
return response;
}
const pageItems = await this.getPageItemsWithValues(feedbackId, page, {
cmId: options.cmId,
readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE,
siteId: options.siteId,
});
// Check completion.
if (changePage == 1 && !pageItems.hasnextpage) {
response.completed = true;
return response;
}
const loadPage = await this.getPageJumpTo(feedbackId, page + changePage, changePage, options);
if (loadPage === false) {
// Completed or first page.
if (changePage == -1) {
response.jumpto = 0;
} else {
response.completed = true;
}
} else {
response.jumpto = loadPage;
}
return response;
};
if (!CoreNetwork.isOnline()) {
// App is offline, store the action.
return storeOffline();
}
// If there's already a response to be sent to the server, discard it first.
await AddonModFeedbackOffline.deleteFeedbackPageResponses(feedbackId, page, options.siteId);
try {
return await this.processPageOnline(feedbackId, page, responses, !!options.goPrevious, options.siteId);
} catch (error) {
if (CoreUtils.isWebServiceError(error)) {
// The WebService has thrown an error, this means that responses cannot be submitted.
throw error;
}
// Couldn't connect to server, store in offline.
return storeOffline();
}
}
/**
* Process a jump between pages.
*
* @param feedbackId Feedback ID.
* @param page The page being processed.
* @param responses The data to be processed the key is the field name (usually type[index]_id).
* @param goPrevious Whether we want to jump to previous page.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the info is retrieved.
*/
async processPageOnline(
feedbackId: number,
page: number,
responses: Record<string, AddonModFeedbackResponseValue>,
goPrevious: boolean,
siteId?: string,
): Promise<AddonModFeedbackProcessPageWSResponse> {
const site = await CoreSites.getSite(siteId);
const params: AddonModFeedbackProcessPageWSParams = {
feedbackid: feedbackId,
page: page,
responses: CoreUtils.objectToArrayOfObjects(responses, 'name', 'value'),
goprevious: goPrevious,
};
const response = await site.write<AddonModFeedbackProcessPageWSResponse>('mod_feedback_process_page', params);
// Invalidate and update current values because they will change.
await CoreUtils.ignoreErrors(this.invalidateCurrentValuesData(feedbackId, site.getId()));
await CoreUtils.ignoreErrors(this.getCurrentValues(feedbackId, { siteId: site.getId() }));
return response;
}
}
export const AddonModFeedback = makeSingleton(AddonModFeedbackProvider);
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 {
[AddonModFeedbackProvider.FORM_SUBMITTED]: AddonModFeedbackFormSubmittedData;
[AddonModFeedbackSyncProvider.AUTO_SYNCED]: AddonModFeedbackAutoSyncData;
}
}
/**
* Data passed to FORM_SUBMITTED event.
*/
export type AddonModFeedbackFormSubmittedData = {
feedbackId: number;
tab: string;
offline: boolean;
};
/**
* Params of mod_feedback_get_analysis WS.
*/
export type AddonModFeedbackGetAnalysisWSParams = {
feedbackid: number; // Feedback instance id.
groupid?: number; // Group id, 0 means that the function will determine the user group.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Data returned by mod_feedback_get_analysis WS.
*/
export type AddonModFeedbackGetAnalysisWSResponse = {
completedcount: number; // Number of completed submissions.
itemscount: number; // Number of items (questions).
itemsdata: {
item: AddonModFeedbackWSItem;
data: string[];
}[];
warnings?: CoreWSExternalWarning[];
};
/**
* Item data returneds by feedback_item_exporter.
*/
export type AddonModFeedbackWSItem = {
id: number; // The record id.
feedback: number; // The feedback instance id this records belongs to.
template: number; // If it belogns to a template, the template id.
name: string; // The item name.
label: string; // The item label.
presentation: string; // The text describing the item or the available possible answers.
typ: string; // The type of the item.
hasvalue: number; // Whether it has a value or not.
position: number; // The position in the list of questions.
required: boolean; // Whether is a item (question) required or not.
dependitem: number; // The item id this item depend on.
dependvalue: string; // The depend value.
options: string; // Different additional settings for the item (question).
itemfiles: CoreWSStoredFile[]; // Itemfiles.
itemnumber: number; // The item position number.
otherdata: string; // Additional data that may be required by external functions.
};
/**
* Item with some calculated data.
*/
export type AddonModFeedbackItem = AddonModFeedbackWSItem & {
rawValue?: AddonModFeedbackResponseValue;
};
/**
* Params of mod_feedback_get_current_completed_tmp WS.
*/
export type AddonModFeedbackGetCurrentCompletedTmpWSParams = {
feedbackid: number; // Feedback instance id.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Data returned by mod_feedback_get_current_completed_tmp WS.
*/
export type AddonModFeedbackGetCurrentCompletedTmpWSResponse = {
feedback: {
id: number; // The record id.
feedback: number; // The feedback instance id this records belongs to.
userid: number; // The user who completed the feedback (0 for anonymous).
guestid: string; // For guests, this is the session key.
timemodified: number; // The last time the feedback was completed.
// eslint-disable-next-line @typescript-eslint/naming-convention
random_response: number; // The response number (used when shuffling anonymous responses).
// eslint-disable-next-line @typescript-eslint/naming-convention
anonymous_response: number; // Whether is an anonymous response.
courseid: number; // The course id where the feedback was completed.
};
warnings?: CoreWSExternalWarning[];
};
/**
* Params of mod_feedback_get_unfinished_responses WS.
*/
export type AddonModFeedbackGetUnfinishedResponsesWSParams = {
feedbackid: number; // Feedback instance id.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Data returned by mod_feedback_get_unfinished_responses WS.
*/
export type AddonModFeedbackGetUnfinishedResponsesWSResponse = {
responses: AddonModFeedbackWSUnfinishedResponse[];
warnings?: CoreWSExternalWarning[];
};
/**
* Unfinished response data returned by feedback_valuetmp_exporter.
*/
export type AddonModFeedbackWSUnfinishedResponse = {
id: number; // The record id.
// eslint-disable-next-line @typescript-eslint/naming-convention
course_id: number; // The course id this record belongs to.
item: number; // The item id that was responded.
completed: number; // Reference to the feedback_completedtmp table.
// eslint-disable-next-line @typescript-eslint/naming-convention
tmp_completed: number; // Old field - not used anymore.
value: string; // The response value.
};
/**
* Params of mod_feedback_get_finished_responses WS.
*/
export type AddonModFeedbackGetFinishedResponsesWSParams = {
feedbackid: number; // Feedback instance id.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Data returned by mod_feedback_get_finished_responses WS.
*/
export type AddonModFeedbackGetFinishedResponsesWSResponse = {
responses: AddonModFeedbackWSFinishedResponse[];
warnings?: CoreWSExternalWarning[];
};
/**
* Unfinished response data returned by feedback_value_exporter.
*/
export type AddonModFeedbackWSFinishedResponse = {
id: number; // The record id.
// eslint-disable-next-line @typescript-eslint/naming-convention
course_id: number; // The course id this record belongs to.
item: number; // The item id that was responded.
completed: number; // Reference to the feedback_completed table.
// eslint-disable-next-line @typescript-eslint/naming-convention
tmp_completed: number; // Old field - not used anymore.
value: string; // The response value.
};
/**
* A response, either finished or unfinished.
*/
export type AddonModFeedbackWSResponse = AddonModFeedbackWSFinishedResponse | AddonModFeedbackWSUnfinishedResponse;
/**
* Params of mod_feedback_get_feedback_access_information WS.
*/
export type AddonModFeedbackGetFeedbackAccessInformationWSParams = {
feedbackid: number; // Feedback instance id.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Data returned by mod_feedback_get_feedback_access_information WS.
*/
export type AddonModFeedbackGetFeedbackAccessInformationWSResponse = {
canviewanalysis: boolean; // Whether the user can view the analysis or not.
cancomplete: boolean; // Whether the user can complete the feedback or not.
cansubmit: boolean; // Whether the user can submit the feedback or not.
candeletesubmissions: boolean; // Whether the user can delete submissions or not.
canviewreports: boolean; // Whether the user can view the feedback reports or not.
canedititems: boolean; // Whether the user can edit feedback items or not.
isempty: boolean; // Whether the feedback has questions or not.
isopen: boolean; // Whether the feedback has active access time restrictions or not.
isalreadysubmitted: boolean; // Whether the feedback is already submitted or not.
isanonymous: boolean; // Whether the feedback is anonymous or not.
warnings?: CoreWSExternalWarning[];
};
/**
* Params of mod_feedback_get_feedbacks_by_courses WS.
*/
export type AddonModFeedbackGetFeedbacksByCoursesWSParams = {
courseids?: number[]; // Array of course ids.
};
/**
* Data returned by mod_feedback_get_feedbacks_by_courses WS.
*/
export type AddonModFeedbackGetFeedbacksByCoursesWSResponse = {
feedbacks: AddonModFeedbackWSFeedback[];
warnings?: CoreWSExternalWarning[];
};
/**
* Feedback data returned by mod_feedback_get_feedbacks_by_courses WS.
*/
export type AddonModFeedbackWSFeedback = {
id: number; // The primary key of the record.
course: number; // Course id this feedback is part of.
name: string; // Feedback name.
intro: string; // Feedback introduction text.
introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
anonymous: number; // Whether the feedback is anonymous.
// eslint-disable-next-line @typescript-eslint/naming-convention
email_notification?: boolean; // Whether email notifications will be sent to teachers.
// eslint-disable-next-line @typescript-eslint/naming-convention
multiple_submit: boolean; // Whether multiple submissions are allowed.
autonumbering: boolean; // Whether questions should be auto-numbered.
// eslint-disable-next-line @typescript-eslint/naming-convention
site_after_submit?: string; // Link to next page after submission.
// eslint-disable-next-line @typescript-eslint/naming-convention
page_after_submit?: string; // Text to display after submission.
// eslint-disable-next-line @typescript-eslint/naming-convention
page_after_submitformat?: number; // Page_after_submit format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
// eslint-disable-next-line @typescript-eslint/naming-convention
publish_stats: boolean; // Whether stats should be published.
timeopen?: number; // Allow answers from this time.
timeclose?: number; // Allow answers until this time.
timemodified?: number; // The time this record was modified.
completionsubmit: boolean; // If set to 1, then the activity will be automatically marked as complete on submission.
coursemodule: number; // Coursemodule.
introfiles: CoreWSExternalFile[]; // Introfiles.
pageaftersubmitfiles?: CoreWSExternalFile[]; // Pageaftersubmitfiles.
};
/**
* Params of mod_feedback_get_items WS.
*/
export type AddonModFeedbackGetItemsWSParams = {
feedbackid: number; // Feedback instance id.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Data returned by mod_feedback_get_items WS.
*/
export type AddonModFeedbackGetItemsWSResponse = {
items: AddonModFeedbackWSItem[];
warnings?: CoreWSExternalWarning[];
};
/**
* Params of mod_feedback_get_non_respondents WS.
*/
export type AddonModFeedbackGetNonRespondentsWSParams = {
feedbackid: number; // Feedback instance id.
groupid?: number; // Group id, 0 means that the function will determine the user group.
sort?: string; // Sort param, must be firstname, lastname or lastaccess (default).
page?: number; // The page of records to return.
perpage?: number; // The number of records to return per page.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Data returned by mod_feedback_get_non_respondents WS.
*/
export type AddonModFeedbackGetNonRespondentsWSResponse = {
users: AddonModFeedbackWSNonRespondent[];
total: number; // Total number of non respondents.
warnings?: CoreWSExternalWarning[];
};
/**
* Data returned by mod_feedback_get_non_respondents WS.
*/
export type AddonModFeedbackWSNonRespondent = {
courseid: number; // Course id.
userid: number; // The user id.
fullname: string; // User full name.
started: boolean; // If the user has started the attempt.
};
/**
* Params of mod_feedback_get_page_items WS.
*/
export type AddonModFeedbackGetPageItemsWSParams = {
feedbackid: number; // Feedback instance id.
page: number; // The page to get starting by 0.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Data returned by mod_feedback_get_page_items WS.
*/
export type AddonModFeedbackGetPageItemsWSResponse = {
items: AddonModFeedbackWSItem[];
hasprevpage: boolean; // Whether is a previous page.
hasnextpage: boolean; // Whether there are more pages.
warnings?: CoreWSExternalWarning[];
};
/**
* Page items with some calculated data.
*/
export type AddonModFeedbackPageItems = Omit<AddonModFeedbackGetPageItemsWSResponse, 'items'> & {
items: AddonModFeedbackItem[];
};
/**
* Params of mod_feedback_get_responses_analysis WS.
*/
export type AddonModFeedbackGetResponsesAnalysisWSParams = {
feedbackid: number; // Feedback instance id.
groupid?: number; // Group id, 0 means that the function will determine the user group.
page?: number; // The page of records to return.
perpage?: number; // The number of records to return per page.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Data returned by mod_feedback_get_responses_analysis WS.
*/
export type AddonModFeedbackGetResponsesAnalysisWSResponse = {
attempts: AddonModFeedbackWSAttempt[];
totalattempts: number; // Total responses count.
anonattempts: AddonModFeedbackWSAnonAttempt[];
totalanonattempts: number; // Total anonymous responses count.
warnings?: CoreWSExternalWarning[];
};
/**
* Attempt data returned by mod_feedback_get_responses_analysis WS.
*/
export type AddonModFeedbackWSAttempt = {
id: number; // Completed id.
courseid: number; // Course id.
userid: number; // User who responded.
timemodified: number; // Time modified for the response.
fullname: string; // User full name.
responses: AddonModFeedbackWSAttemptResponse[];
};
/**
* Anonymous attempt data returned by mod_feedback_get_responses_analysis WS.
*/
export type AddonModFeedbackWSAnonAttempt = {
id: number; // Completed id.
courseid: number; // Course id.
// eslint-disable-next-line id-blacklist
number: number; // Response number.
responses: AddonModFeedbackWSAttemptResponse[];
};
/**
* Response data returned by mod_feedback_get_responses_analysis WS.
*/
export type AddonModFeedbackWSAttemptResponse = {
id: number; // Response id.
name: string; // Response name.
printval: string; // Response ready for output.
rawval: string; // Response raw value.
};
/**
* Params of mod_feedback_launch_feedback WS.
*/
export type AddonModFeedbackLaunchFeedbackWSParams = {
feedbackid: number; // Feedback instance id.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Data returned by mod_feedback_launch_feedback WS.
*/
export type AddonModFeedbackLaunchFeedbackWSResponse = {
gopage: number; // The next page to go (-1 if we were already in the last page). 0 for first page.
warnings?: CoreWSExternalWarning[];
};
/**
* Params of mod_feedback_get_last_completed WS.
*/
export type AddonModFeedbackGetLastCompletedWSParams = {
feedbackid: number; // Feedback instance id.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Params of mod_feedback_view_feedback WS.
*/
export type AddonModFeedbackViewFeedbackWSParams = {
feedbackid: number; // Feedback instance id.
moduleviewed?: boolean; // If we need to mark the module as viewed for completion.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Params of mod_feedback_process_page WS.
*/
export type AddonModFeedbackProcessPageWSParams = {
feedbackid: number; // Feedback instance id.
page: number; // The page being processed.
responses?: { // The data to be processed.
name: string; // The response name (usually type[index]_id).
value: string | number; // The response value.
}[];
goprevious?: boolean; // Whether we want to jump to previous page.
courseid?: number; // Course where user completes the feedback (for site feedbacks only).
};
/**
* Data returned by mod_feedback_process_page WS.
*/
export type AddonModFeedbackProcessPageWSResponse = {
jumpto: number; // The page to jump to.
completed: boolean; // If the user completed the feedback.
completionpagecontents: string; // The completion page contents.
siteaftersubmit: string; // The link (could be relative) to show after submit.
warnings?: CoreWSExternalWarning[];
};
/**
* Data returned by process page.
*/
export type AddonModFeedbackProcessPageResponse = {
jumpto: number | null; // The page to jump to.
completed: boolean; // If the user completed the feedback.
offline?: boolean; // Whether data has been stored in offline.
} & Partial<AddonModFeedbackProcessPageWSResponse>;
/**
* Common options with a group ID.
*/
export type AddonModFeedbackGroupOptions = CoreCourseCommonModWSOptions & {
groupId?: number; // Group id, 0 means that the function will determine the user group. Defaults to 0.
};
/**
* Common options with a group ID and page.
*/
export type AddonModFeedbackGroupPaginatedOptions = AddonModFeedbackGroupOptions & {
page?: number; // The page of records to return. The page of records to return.
};
/**
* Common options with a group ID and page.
*/
export type AddonModFeedbackProcessPageOptions = {
goPrevious?: boolean; // Whether we want to jump to previous page.
formHasErrors?: boolean; // Whether the form we sent has required but empty fields (only used in offline).
cmId?: number; // Module ID.
courseId?: number; // Course ID the feedback belongs to.
siteId?: string; // Site ID. If not defined, current site.;
};
/**
* Possible types of responses.
*/
export type AddonModFeedbackResponseValue = string | number;
type OfflineResponsesArray = {
id: string;
value: AddonModFeedbackResponseValue;
}[];
/**
* Previous non respondents when using recursive function.
*/
export type AddonModFeedbackPreviousNonRespondents = {
page: number;
users: AddonModFeedbackWSNonRespondent[];
};
/**
* All non respondents.
*/
export type AddonModFeedbackAllNonRespondent = AddonModFeedbackPreviousNonRespondents & {
total: number;
};
export type AddonModFeedbackPreviousResponsesAnalysis = {
page: number;
attempts: AddonModFeedbackWSAttempt[];
anonattempts: AddonModFeedbackWSAnonAttempt[];
};
export type AddonModFeedbackAllResponsesAnalysis = AddonModFeedbackPreviousResponsesAnalysis & {
totalattempts: number;
totalanonattempts: number;
};
export type AddonModFeedbackGetAttemptPreviousData = {
page: number;
attemptsLoaded: number;
anonAttemptsLoaded: number;
};