449 lines
17 KiB
TypeScript
449 lines
17 KiB
TypeScript
// (C) Copyright 2015 Martin Dougiamas
|
|
//
|
|
// 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 { NavController, ViewController } from 'ionic-angular';
|
|
import { AddonModFeedbackProvider } from './feedback';
|
|
import { CoreUserProvider } from '@core/user/providers/user';
|
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
|
|
/**
|
|
* Service that provides helper functions for feedbacks.
|
|
*/
|
|
@Injectable()
|
|
export class AddonModFeedbackHelperProvider {
|
|
|
|
protected MODE_RESPONSETIME = 1;
|
|
protected MODE_COURSE = 2;
|
|
protected MODE_CATEGORY = 3;
|
|
|
|
constructor(protected feedbackProvider: AddonModFeedbackProvider, protected userProvider: CoreUserProvider,
|
|
protected textUtils: CoreTextUtilsProvider, protected translate: TranslateService,
|
|
protected timeUtils: CoreTimeUtilsProvider) {
|
|
}
|
|
|
|
/**
|
|
* Check if the page we are going to open is in the history and returns the view controller in the stack to go back.
|
|
*
|
|
* @param {string} pageName Name of the page we want to navigate.
|
|
* @param {number} instance Activity instance Id. I.e FeedbackId.
|
|
* @param {string} paramName Param name where to find the instance number.
|
|
* @param {string} prefix Prefix to check if we are out of the activity context.
|
|
* @param {NavController} navCtrl Nav Controller of the view.
|
|
* @return {ViewController} Returns view controller found or null.
|
|
*/
|
|
protected getPageView(pageName: string, instance: number, paramName: string, prefix: string,
|
|
navCtrl: NavController): ViewController {
|
|
let historyInstance, params,
|
|
view = navCtrl.getActive();
|
|
|
|
while (!view.isFirst()) {
|
|
if (!view.name.startsWith(prefix)) {
|
|
break;
|
|
}
|
|
|
|
params = view.getNavParams();
|
|
|
|
historyInstance = params.get(paramName) ? params.get(paramName) : params.get('module').instance;
|
|
|
|
// Check we are not changing to another activity.
|
|
if (!historyInstance || historyInstance != instance) {
|
|
break;
|
|
}
|
|
|
|
// Page found.
|
|
if (view.name == pageName) {
|
|
return view;
|
|
}
|
|
|
|
view = navCtrl.getPrevious(view);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a list of students who didn't submit the feedback with extra info.
|
|
*
|
|
* @param {number} feedbackId Feedback ID.
|
|
* @param {number} groupId Group id, 0 means that the function will determine the user group.
|
|
* @param {number} page The page of records to return.
|
|
* @return {Promise<any>} Promise resolved when the info is retrieved.
|
|
*/
|
|
getNonRespondents(feedbackId: number, groupId: number, page: number): Promise<any> {
|
|
return this.feedbackProvider.getNonRespondents(feedbackId, groupId, page).then((responses) => {
|
|
return this.addImageProfileToAttempts(responses.users).then((users) => {
|
|
responses.users = users;
|
|
|
|
return responses;
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get page items responses to be sent.
|
|
*
|
|
* @param {any[]} items Items where the values are.
|
|
* @return {any} Responses object to be sent.
|
|
*/
|
|
getPageItemsResponses(items: any[]): any {
|
|
const responses = {};
|
|
|
|
items.forEach((itemData) => {
|
|
let answered = false;
|
|
|
|
itemData.hasError = false;
|
|
|
|
if (itemData.typ == 'captcha') {
|
|
const value = itemData.value || '',
|
|
name = itemData.typ + '_' + itemData.id;
|
|
|
|
answered = !!value;
|
|
responses[name] = 1;
|
|
responses['g-recaptcha-response'] = value;
|
|
responses['recaptcha_element'] = 'dummyvalue';
|
|
|
|
if (itemData.required && !answered) {
|
|
// Check if it has any value.
|
|
itemData.isEmpty = true;
|
|
} else {
|
|
itemData.isEmpty = false;
|
|
}
|
|
} else if (itemData.hasvalue) {
|
|
let name, value;
|
|
const nameTemp = itemData.typ + '_' + itemData.id;
|
|
|
|
if (itemData.typ == 'multichoice' && itemData.subtype == 'c') {
|
|
name = nameTemp + '[0]';
|
|
responses[name] = 0;
|
|
itemData.choices.forEach((choice, index) => {
|
|
name = nameTemp + '[' + (index + 1) + ']';
|
|
value = choice.checked ? choice.value : 0;
|
|
if (!answered && value) {
|
|
answered = true;
|
|
}
|
|
responses[name] = value;
|
|
});
|
|
} else {
|
|
if (itemData.typ == 'multichoice') {
|
|
name = nameTemp + '[0]';
|
|
} else {
|
|
name = nameTemp;
|
|
}
|
|
|
|
if (itemData.typ == 'multichoice' || itemData.typ == 'multichoicerated') {
|
|
value = itemData.value || 0;
|
|
} else if (itemData.typ == 'numeric') {
|
|
value = itemData.value || itemData.value == 0 ? itemData.value : '';
|
|
|
|
if (value != '') {
|
|
if ((itemData.rangefrom != '' && value < itemData.rangefrom) ||
|
|
(itemData.rangeto != '' && value > itemData.rangeto)) {
|
|
itemData.hasError = true;
|
|
}
|
|
}
|
|
} else {
|
|
value = itemData.value || itemData.value == 0 ? itemData.value : '';
|
|
}
|
|
|
|
answered = !!value;
|
|
responses[name] = value;
|
|
}
|
|
|
|
if (itemData.required && !answered) {
|
|
// Check if it has any value.
|
|
itemData.isEmpty = true;
|
|
} else {
|
|
itemData.isEmpty = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
return responses;
|
|
}
|
|
|
|
/**
|
|
* Returns the feedback user responses with extra info.
|
|
*
|
|
* @param {number} feedbackId Feedback ID.
|
|
* @param {number} groupId Group id, 0 means that the function will determine the user group.
|
|
* @param {number} page The page of records to return.
|
|
* @return {Promise<any>} Promise resolved when the info is retrieved.
|
|
*/
|
|
getResponsesAnalysis(feedbackId: number, groupId: number, page: number): Promise<any> {
|
|
return this.feedbackProvider.getResponsesAnalysis(feedbackId, groupId, page).then((responses) => {
|
|
return this.addImageProfileToAttempts(responses.attempts).then((attempts) => {
|
|
responses.attempts = attempts;
|
|
|
|
return responses;
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Add Image profile url field on attempts
|
|
*
|
|
* @param {any} attempts Attempts array to get profile from.
|
|
* @return {Promise<any>} Returns the same array with the profileimageurl added if found.
|
|
*/
|
|
protected addImageProfileToAttempts(attempts: any): Promise<any> {
|
|
const promises = attempts.map((attempt) => {
|
|
return this.userProvider.getProfile(attempt.userid, attempt.courseid, true).then((user) => {
|
|
attempt.profileimageurl = user.profileimageurl;
|
|
}).catch(() => {
|
|
// Error getting profile, resolve promise without adding any extra data.
|
|
});
|
|
});
|
|
|
|
return Promise.all(promises).then(() => {
|
|
return attempts;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper function to open a feature in the app.
|
|
*
|
|
* @param {string} feature Name of the feature to open.
|
|
* @param {NavController} navCtrl NavController.
|
|
* @param {any} module Course module activity object.
|
|
* @param {number} courseId Course Id.
|
|
* @param {number} [group=0] Course module activity object.
|
|
* @return {Promise<void>} Resolved when navigation animation is done.
|
|
*/
|
|
openFeature(feature: string, navCtrl: NavController, module: any, courseId: number, group: number = 0): Promise<void> {
|
|
const pageName = feature && feature != 'analysis' ? 'AddonModFeedback' + feature + 'Page' : 'AddonModFeedbackIndexPage',
|
|
stateParams = {
|
|
module: module,
|
|
moduleId: module.id,
|
|
courseId: courseId,
|
|
feedbackId: module.instance,
|
|
group: group
|
|
};
|
|
|
|
// Only check history if navigating through tabs.
|
|
if (pageName == 'AddonModFeedbackIndexPage') {
|
|
stateParams['tab'] = feature == 'analysis' ? 'analysis' : 'overview';
|
|
const view = this.getPageView(pageName, module.instance, 'feedbackId', 'AddonModFeedback', navCtrl);
|
|
|
|
if (view) {
|
|
// Go back to the found page.
|
|
return navCtrl.popTo(view);
|
|
}
|
|
}
|
|
|
|
// Not found, open new state.
|
|
return navCtrl.push(pageName, stateParams);
|
|
}
|
|
|
|
/**
|
|
* Helper funtion for item type Label.
|
|
*
|
|
* @param {any} item Item to process.
|
|
* @return {any} Item processed to show form.
|
|
*/
|
|
protected getItemFormLabel(item: any): any {
|
|
item.template = 'label';
|
|
item.name = '';
|
|
item.presentation = this.textUtils.replacePluginfileUrls(item.presentation, item.itemfiles);
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Helper funtion for item type Info.
|
|
*
|
|
* @param {any} item Item to process.
|
|
* @return {any} Item processed to show form.
|
|
*/
|
|
protected getItemFormInfo(item: any): any {
|
|
item.template = 'label';
|
|
|
|
const type = parseInt(item.presentation, 10);
|
|
|
|
if (type == this.MODE_COURSE || type == this.MODE_CATEGORY) {
|
|
item.presentation = item.otherdata;
|
|
item.value = typeof item.rawValue != 'undefined' ? item.rawValue : item.otherdata;
|
|
} else if (type == this.MODE_RESPONSETIME) {
|
|
item.value = '__CURRENT__TIMESTAMP__';
|
|
const tempValue = typeof item.rawValue != 'undefined' ? item.rawValue * 1000 : new Date().getTime();
|
|
item.presentation = this.timeUtils.userDate(tempValue);
|
|
} else {
|
|
// Errors on item, return false.
|
|
return false;
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Helper funtion for item type Numeric.
|
|
*
|
|
* @param {any} item Item to process.
|
|
* @return {any} Item processed to show form.
|
|
*/
|
|
protected getItemFormNumeric(item: any): any {
|
|
item.template = 'numeric';
|
|
|
|
const range = item.presentation.split(AddonModFeedbackProvider.LINE_SEP) || [];
|
|
item.rangefrom = range.length > 0 ? parseInt(range[0], 10) || '' : '';
|
|
item.rangeto = range.length > 1 ? parseInt(range[1], 10) || '' : '';
|
|
item.value = typeof item.rawValue != 'undefined' ? parseFloat(item.rawValue) : '';
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Helper funtion for item type Text field.
|
|
*
|
|
* @param {any} item Item to process.
|
|
* @return {any} Item processed to show form.
|
|
*/
|
|
protected getItemFormTextfield(item: any): any {
|
|
item.template = 'textfield';
|
|
item.length = item.presentation.split(AddonModFeedbackProvider.LINE_SEP)[1] || 255;
|
|
item.value = typeof item.rawValue != 'undefined' ? item.rawValue : '';
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Helper funtion for item type Textarea.
|
|
*
|
|
* @param {any} item Item to process.
|
|
* @return {any} Item processed to show form.
|
|
*/
|
|
protected getItemFormTextarea(item: any): any {
|
|
item.template = 'textarea';
|
|
item.value = typeof item.rawValue != 'undefined' ? item.rawValue : '';
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Helper funtion for item type Multichoice.
|
|
*
|
|
* @param {any} item Item to process.
|
|
* @return {any} Item processed to show form.
|
|
*/
|
|
protected getItemFormMultichoice(item: any): any {
|
|
let parts = item.presentation.split(AddonModFeedbackProvider.MULTICHOICE_TYPE_SEP) || [];
|
|
item.subtype = parts.length > 0 && parts[0] ? parts[0] : 'r';
|
|
item.template = 'multichoice-' + item.subtype;
|
|
|
|
item.presentation = parts.length > 1 ? parts[1] : '';
|
|
if (item.subtype != 'd') {
|
|
parts = item.presentation.split(AddonModFeedbackProvider.MULTICHOICE_ADJUST_SEP) || [];
|
|
item.presentation = parts.length > 0 ? parts[0] : '';
|
|
// Horizontal are not supported right now. item.horizontal = parts.length > 1 && !!parts[1];
|
|
}
|
|
|
|
item.choices = item.presentation.split(AddonModFeedbackProvider.LINE_SEP) || [];
|
|
item.choices = item.choices.map((choice, index) => {
|
|
const weightValue = choice.split(AddonModFeedbackProvider.MULTICHOICERATED_VALUE_SEP) || [''];
|
|
choice = weightValue.length == 1 ? weightValue[0] : '(' + weightValue[0] + ') ' + weightValue[1];
|
|
|
|
return {value: index + 1, label: choice};
|
|
});
|
|
|
|
if (item.subtype === 'r' && item.options.search(AddonModFeedbackProvider.MULTICHOICE_HIDENOSELECT) == -1) {
|
|
item.choices.unshift({value: 0, label: this.translate.instant('addon.mod_feedback.not_selected')});
|
|
item.value = typeof item.rawValue != 'undefined' ? parseInt(item.rawValue, 10) : 0;
|
|
} else if (item.subtype === 'd') {
|
|
item.choices.unshift({value: 0, label: ''});
|
|
item.value = typeof item.rawValue != 'undefined' ? parseInt(item.rawValue, 10) : 0;
|
|
} else if (item.subtype === 'c') {
|
|
if (typeof item.rawValue == 'undefined') {
|
|
item.value = '';
|
|
} else {
|
|
item.rawValue = '' + item.rawValue;
|
|
const values = item.rawValue.split(AddonModFeedbackProvider.LINE_SEP);
|
|
item.choices.forEach((choice) => {
|
|
for (const x in values) {
|
|
if (choice.value == values[x]) {
|
|
choice.checked = true;
|
|
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
item.value = typeof item.rawValue != 'undefined' ? parseInt(item.rawValue, 10) : '';
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Helper funtion for item type Captcha.
|
|
*
|
|
* @param {any} item Item to process.
|
|
* @return {any} Item processed to show form.
|
|
*/
|
|
protected getItemFormCaptcha(item: any): any {
|
|
const data = this.textUtils.parseJSON(item.otherdata);
|
|
if (data && data.length > 3) {
|
|
item.captcha = {
|
|
recaptchapublickey: data[3]
|
|
};
|
|
}
|
|
item.template = 'captcha';
|
|
item.value = '';
|
|
|
|
return item;
|
|
}
|
|
|
|
/**
|
|
* Process and returns item to print form.
|
|
*
|
|
* @param {any} item Item to process.
|
|
* @param {boolean} preview Previewing options.
|
|
* @return {any} Item processed to show form.
|
|
*/
|
|
getItemForm(item: any, preview: boolean): any {
|
|
switch (item.typ) {
|
|
case 'label':
|
|
return this.getItemFormLabel(item);
|
|
case 'info':
|
|
return this.getItemFormInfo(item);
|
|
case 'numeric':
|
|
return this.getItemFormNumeric(item);
|
|
case 'textfield':
|
|
return this.getItemFormTextfield(item);
|
|
case 'textarea':
|
|
return this.getItemFormTextarea(item);
|
|
case 'multichoice':
|
|
return this.getItemFormMultichoice(item);
|
|
case 'multichoicerated':
|
|
return this.getItemFormMultichoice(item);
|
|
case 'pagebreak':
|
|
if (!preview) {
|
|
// Pagebreaks are only used on preview.
|
|
return false;
|
|
}
|
|
break;
|
|
case 'captcha':
|
|
// Captcha is not supported right now. However label will be shown.
|
|
return this.getItemFormCaptcha(item);
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
}
|