2018-04-12 14:56:51 +02:00
|
|
|
// (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 { CoreSitesProvider } from '@providers/sites';
|
|
|
|
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
|
|
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
|
|
import { AddonModDataFieldsDelegate } from './fields-delegate';
|
|
|
|
import { AddonModDataOfflineProvider } from './offline';
|
|
|
|
import { AddonModDataProvider } from './data';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Service that provides helper functions for datas.
|
|
|
|
*/
|
|
|
|
@Injectable()
|
|
|
|
export class AddonModDataHelperProvider {
|
|
|
|
|
2018-05-16 16:12:54 +02:00
|
|
|
constructor(private sitesProvider: CoreSitesProvider, protected dataProvider: AddonModDataProvider,
|
2018-04-12 14:56:51 +02:00
|
|
|
private translate: TranslateService, private fieldsDelegate: AddonModDataFieldsDelegate,
|
|
|
|
private dataOffline: AddonModDataOfflineProvider, private fileUploaderProvider: CoreFileUploaderProvider) { }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the record with the offline actions applied.
|
|
|
|
*
|
|
|
|
* @param {any} record Entry to modify.
|
|
|
|
* @param {any} offlineActions Offline data with the actions done.
|
|
|
|
* @param {any} fields Entry defined fields indexed by fieldid.
|
|
|
|
* @return {any} Modified entry.
|
|
|
|
*/
|
|
|
|
applyOfflineActions(record: any, offlineActions: any[], fields: any[]): any {
|
|
|
|
const promises = [];
|
|
|
|
|
|
|
|
offlineActions.forEach((action) => {
|
|
|
|
switch (action.action) {
|
|
|
|
case 'approve':
|
|
|
|
record.approved = true;
|
|
|
|
break;
|
|
|
|
case 'disapprove':
|
|
|
|
record.approved = false;
|
|
|
|
break;
|
|
|
|
case 'delete':
|
|
|
|
record.deleted = true;
|
|
|
|
break;
|
|
|
|
case 'add':
|
|
|
|
case 'edit':
|
|
|
|
const offlineContents = {};
|
|
|
|
|
|
|
|
action.fields.forEach((offlineContent) => {
|
|
|
|
if (typeof offlineContents[offlineContent.fieldid] == 'undefined') {
|
|
|
|
offlineContents[offlineContent.fieldid] = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (offlineContent.subfield) {
|
|
|
|
offlineContents[offlineContent.fieldid][offlineContent.subfield] = JSON.parse(offlineContent.value);
|
|
|
|
} else {
|
|
|
|
offlineContents[offlineContent.fieldid][''] = JSON.parse(offlineContent.value);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Override field contents.
|
|
|
|
fields.forEach((field) => {
|
|
|
|
if (this.fieldsDelegate.hasFiles(field)) {
|
|
|
|
promises.push(this.getStoredFiles(record.dataid, record.id, field.id).then((offlineFiles) => {
|
|
|
|
record.contents[field.id] = this.fieldsDelegate.overrideData(field, record.contents[field.id],
|
|
|
|
offlineContents[field.id], offlineFiles);
|
|
|
|
}));
|
|
|
|
} else {
|
|
|
|
record.contents[field.id] = this.fieldsDelegate.overrideData(field, record.contents[field.id],
|
|
|
|
offlineContents[field.id]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return Promise.all(promises).then(() => {
|
|
|
|
return record;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Displays fields for being shown.
|
|
|
|
*
|
|
|
|
* @param {string} template Template HMTL.
|
|
|
|
* @param {any[]} fields Fields that defines every content in the entry.
|
|
|
|
* @param {any} entry Entry.
|
|
|
|
* @param {string} mode Mode list or show.
|
|
|
|
* @param {any} actions Actions that can be performed to the record.
|
|
|
|
* @return {string} Generated HTML.
|
|
|
|
*/
|
|
|
|
displayShowFields(template: string, fields: any[], entry: any, mode: string, actions: any): string {
|
|
|
|
if (!template) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
let replace, render;
|
|
|
|
|
|
|
|
// Replace the fields found on template.
|
|
|
|
fields.forEach((field) => {
|
|
|
|
replace = '[[' + field.name + ']]';
|
|
|
|
replace = replace.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
|
|
|
replace = new RegExp(replace, 'gi');
|
|
|
|
|
|
|
|
// Replace field by a generic directive.
|
2018-05-02 10:44:28 +02:00
|
|
|
render = '<addon-mod-data-field-plugin [field]="fields[' + field.id + ']" [value]="entries[' + entry.id +
|
|
|
|
'].contents[' + field.id + ']" mode="' + mode + '" [database]="data" (viewAction)="gotoEntry(' + entry.id +
|
2018-04-12 14:56:51 +02:00
|
|
|
')"></addon-mod-data-field-plugin>';
|
|
|
|
template = template.replace(replace, render);
|
|
|
|
});
|
|
|
|
|
2018-05-08 15:10:32 +02:00
|
|
|
for (const action in actions) {
|
2018-04-12 14:56:51 +02:00
|
|
|
replace = new RegExp('##' + action + '##', 'gi');
|
|
|
|
// Is enabled?
|
|
|
|
if (actions[action]) {
|
|
|
|
if (action == 'moreurl') {
|
|
|
|
// Render more url directly because it can be part of an HTML attribute.
|
|
|
|
render = this.sitesProvider.getCurrentSite().getURL() + '/mod/data/view.php?d={{data.id}}&rid=' + entry.id;
|
|
|
|
} else if (action == 'approvalstatus') {
|
|
|
|
render = this.translate.instant('addon.mod_data.' + (entry.approved ? 'approved' : 'notapproved'));
|
|
|
|
} else {
|
2018-05-08 15:10:32 +02:00
|
|
|
render = '<addon-mod-data-action action="' + action + '" [entry]="entries[' + entry.id +
|
|
|
|
']" mode="' + mode + '" [database]="data"></addon-mod-data-action>';
|
2018-04-12 14:56:51 +02:00
|
|
|
}
|
|
|
|
template = template.replace(replace, render);
|
|
|
|
} else {
|
|
|
|
template = template.replace(replace, '');
|
|
|
|
}
|
2018-05-08 15:10:32 +02:00
|
|
|
}
|
2018-04-12 14:56:51 +02:00
|
|
|
|
|
|
|
return template;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an object with all the actions that the user can do over the record.
|
|
|
|
*
|
|
|
|
* @param {any} database Database activity.
|
|
|
|
* @param {any} accessInfo Access info to the activity.
|
|
|
|
* @param {any} record Entry or record where the actions will be performed.
|
|
|
|
* @return {any} Keyed with the action names and boolean to evalute if it can or cannot be done.
|
|
|
|
*/
|
|
|
|
getActions(database: any, accessInfo: any, record: any): any {
|
|
|
|
return {
|
|
|
|
more: true,
|
|
|
|
moreurl: true,
|
|
|
|
user: true,
|
|
|
|
userpicture: true,
|
|
|
|
timeadded: true,
|
|
|
|
timemodified: true,
|
|
|
|
|
|
|
|
edit: record.canmanageentry && !record.deleted, // This already checks capabilities and readonly period.
|
|
|
|
delete: record.canmanageentry,
|
|
|
|
approve: database.approval && accessInfo.canapprove && !record.approved && !record.deleted,
|
|
|
|
disapprove: database.approval && accessInfo.canapprove && record.approved && !record.deleted,
|
|
|
|
|
|
|
|
approvalstatus: database.approval,
|
|
|
|
comments: database.comments,
|
|
|
|
|
|
|
|
// Unsupported actions.
|
|
|
|
delcheck: false,
|
|
|
|
export: false
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-05-16 16:12:54 +02:00
|
|
|
/**
|
|
|
|
* Fetch all entries and return it's Id
|
|
|
|
*
|
|
|
|
* @param {number} dataId Data ID.
|
|
|
|
* @param {number} groupId Group ID.
|
|
|
|
* @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
|
|
|
|
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
|
|
|
|
* @param {string} [siteId] Site ID. Current if not defined.
|
|
|
|
* @return {Promise<any>} Resolved with an array of entry ID.
|
|
|
|
*/
|
|
|
|
getAllEntriesIds(dataId: number, groupId: number, forceCache: boolean = false, ignoreCache: boolean = false, siteId?: string):
|
|
|
|
Promise<any> {
|
|
|
|
return this.dataProvider.fetchAllEntries(dataId, groupId, undefined, undefined, undefined, forceCache, ignoreCache, siteId)
|
|
|
|
.then((entries) => {
|
|
|
|
return entries.map((entry) => {
|
|
|
|
return entry.id;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get an online or offline entry.
|
|
|
|
*
|
|
|
|
* @param {any} data Database.
|
|
|
|
* @param {number} entryId Entry ID.
|
|
|
|
* @param {any} [offlineActions] Offline data with the actions done. Required for offline entries.
|
|
|
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
|
|
|
* @return {Promise<any>} Promise resolved with the entry.
|
|
|
|
*/
|
|
|
|
getEntry(data: any, entryId: number, offlineActions?: any, siteId?: string): Promise<any> {
|
|
|
|
if (entryId > 0) {
|
|
|
|
// It's an online entry, get it from WS.
|
|
|
|
return this.dataProvider.getEntry(data.id, entryId, siteId);
|
|
|
|
}
|
|
|
|
|
|
|
|
// It's an offline entry, search it in the offline actions.
|
|
|
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
|
|
|
const offlineEntry = offlineActions.find((offlineAction) => {
|
|
|
|
return offlineAction.action == 'add';
|
|
|
|
});
|
|
|
|
|
|
|
|
if (offlineEntry) {
|
|
|
|
const siteInfo = site.getInfo();
|
|
|
|
|
|
|
|
return {entry: {
|
|
|
|
id: offlineEntry.entryid,
|
|
|
|
canmanageentry: true,
|
|
|
|
approved: !data.approval || data.manageapproved,
|
|
|
|
dataid: offlineEntry.dataid,
|
|
|
|
groupid: offlineEntry.groupid,
|
|
|
|
timecreated: -offlineEntry.entryid,
|
|
|
|
timemodified: -offlineEntry.entryid,
|
|
|
|
userid: siteInfo.userid,
|
|
|
|
fullname: siteInfo.fullname,
|
|
|
|
contents: {}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get page info related to an entry.
|
|
|
|
*
|
|
|
|
* @param {number} dataId Data ID.
|
|
|
|
* @param {number} entryId Entry ID.
|
|
|
|
* @param {number} groupId Group ID.
|
|
|
|
* @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
|
|
|
|
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
|
|
|
|
* @param {string} [siteId] Site ID. Current if not defined.
|
|
|
|
* @return {Promise<any>} Containing page number, if has next and have following page.
|
|
|
|
*/
|
|
|
|
getPageInfoByEntry(dataId: number, entryId: number, groupId: number, forceCache: boolean = false,
|
|
|
|
ignoreCache: boolean = false, siteId?: string): Promise<any> {
|
|
|
|
return this.getAllEntriesIds(dataId, groupId, forceCache, ignoreCache, siteId).then((entries) => {
|
|
|
|
const index = entries.findIndex((entry) => {
|
|
|
|
return entry == entryId;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (index >= 0) {
|
|
|
|
return {
|
|
|
|
previousId: entries[index - 1] || false,
|
|
|
|
nextId: entries[index + 1] || false,
|
|
|
|
entryId: entryId,
|
|
|
|
page: index + 1, // Parsed to natural language.
|
|
|
|
numEntries: entries.length
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get page info related to an entry by page number.
|
|
|
|
*
|
|
|
|
* @param {number} dataId Data ID.
|
|
|
|
* @param {number} page Page number.
|
|
|
|
* @param {number} groupId Group ID.
|
|
|
|
* @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
|
|
|
|
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
|
|
|
|
* @param {string} [siteId] Site ID. Current if not defined.
|
|
|
|
* @return {Promise<any>} Containing page number, if has next and have following page.
|
|
|
|
*/
|
|
|
|
getPageInfoByPage(dataId: number, page: number, groupId: number, forceCache: boolean = false,
|
|
|
|
ignoreCache: boolean = false, siteId?: string): Promise<any> {
|
|
|
|
return this.getAllEntriesIds(dataId, groupId, forceCache, ignoreCache, siteId).then((entries) => {
|
|
|
|
const index = page - 1,
|
|
|
|
entryId = entries[index];
|
|
|
|
|
|
|
|
if (entryId) {
|
|
|
|
return {
|
|
|
|
previousId: entries[index - 1] || null,
|
|
|
|
nextId: entries[index + 1] || null,
|
|
|
|
entryId: entryId,
|
|
|
|
page: page, // Parsed to natural language.
|
|
|
|
numEntries: entries.length
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-04-12 14:56:51 +02:00
|
|
|
/**
|
|
|
|
* Get a list of stored attachment files for a new entry. See $mmaModDataHelper#storeFiles.
|
|
|
|
*
|
|
|
|
* @param {number} dataId Database ID.
|
|
|
|
* @param {number} entryId Entry ID or, if creating, timemodified.
|
|
|
|
* @param {number} fieldId Field ID.
|
|
|
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
|
|
|
* @return {Promise<any>} Promise resolved with the files.
|
|
|
|
*/
|
|
|
|
getStoredFiles(dataId: number, entryId: number, fieldId: number, siteId?: string): Promise<any> {
|
|
|
|
return this.dataOffline.getEntryFieldFolder(dataId, entryId, fieldId, siteId).then((folderPath) => {
|
|
|
|
return this.fileUploaderProvider.getStoredFiles(folderPath).catch(() => {
|
|
|
|
// Ignore not found files.
|
|
|
|
return [];
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a prefix to all rules in a CSS string.
|
|
|
|
*
|
|
|
|
* @param {string} css CSS code to be prefixed.
|
|
|
|
* @param {string} prefix Prefix css selector.
|
|
|
|
* @return {string} Prefixed CSS.
|
|
|
|
*/
|
|
|
|
prefixCSS(css: string, prefix: string): string {
|
|
|
|
if (!css) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove comments first.
|
|
|
|
let regExp = /\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm;
|
|
|
|
css = css.replace(regExp, '');
|
|
|
|
// Add prefix.
|
|
|
|
regExp = /([^]*?)({[^]*?}|,)/g;
|
|
|
|
|
|
|
|
return css.replace(regExp, prefix + ' $1 $2');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a list of files (either online files or local files), store the local files in a local folder
|
|
|
|
* to be submitted later.
|
|
|
|
*
|
|
|
|
* @param {number} dataId Database ID.
|
|
|
|
* @param {number} entryId Entry ID or, if creating, timemodified.
|
|
|
|
* @param {number} fieldId Field ID.
|
|
|
|
* @param {any[]} files List of files.
|
|
|
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
|
|
|
* @return {Promise<any>} Promise resolved if success, rejected otherwise.
|
|
|
|
*/
|
|
|
|
storeFiles(dataId: number, entryId: number, fieldId: number, files: any[], siteId?: string): Promise<any> {
|
|
|
|
// Get the folder where to store the files.
|
|
|
|
return this.dataOffline.getEntryFieldFolder(dataId, entryId, fieldId, siteId).then((folderPath) => {
|
|
|
|
return this.fileUploaderProvider.storeFilesToUpload(folderPath, files);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Upload or store some files, depending if the user is offline or not.
|
|
|
|
*
|
|
|
|
* @param {number} dataId Database ID.
|
|
|
|
* @param {number} [itemId=0] Draft ID to use. Undefined or 0 to create a new draft ID.
|
|
|
|
* @param {number} entryId Entry ID or, if creating, timemodified.
|
|
|
|
* @param {number} fieldId Field ID.
|
|
|
|
* @param {any[]} files List of files.
|
|
|
|
* @param {boolean} offline True if files sould be stored for offline, false to upload them.
|
|
|
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
|
|
|
* @return {Promise<any>} Promise resolved if success.
|
|
|
|
*/
|
|
|
|
uploadOrStoreFiles(dataId: number, itemId: number = 0, entryId: number, fieldId: number, files: any[], offline: boolean,
|
|
|
|
siteId?: string): Promise<any> {
|
|
|
|
if (files.length) {
|
|
|
|
if (offline) {
|
|
|
|
return this.storeFiles(dataId, entryId, fieldId, files, siteId);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.fileUploaderProvider.uploadOrReuploadFiles(files, AddonModDataProvider.COMPONENT, itemId, siteId);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.resolve(0);
|
|
|
|
}
|
|
|
|
}
|