commit
f4681c1c64
|
@ -8574,6 +8574,11 @@
|
|||
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==",
|
||||
"dev": true
|
||||
},
|
||||
"font-awesome": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
|
||||
"integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
|
||||
},
|
||||
"for-in": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
"cordova-support-google-services": "^1.2.1",
|
||||
"cordova.plugins.diagnostic": "^6.0.2",
|
||||
"es6-promise-plugin": "^4.2.2",
|
||||
"font-awesome": "^4.7.0",
|
||||
"moment": "^2.29.0",
|
||||
"nl.kingsquare.cordova.background-audio": "^1.0.1",
|
||||
"phonegap-plugin-multidex": "^1.0.0",
|
||||
|
|
|
@ -63,7 +63,7 @@ export class CoreDelegate {
|
|||
/**
|
||||
* Set of promises to update a handler, to prevent doing the same operation twice.
|
||||
*/
|
||||
protected updatePromises: {[siteId: string]: {[name: string]: Promise<any>}} = {};
|
||||
protected updatePromises: {[siteId: string]: {[name: string]: Promise<void>}} = {};
|
||||
|
||||
/**
|
||||
* Whether handlers have been initialized.
|
||||
|
@ -73,7 +73,7 @@ export class CoreDelegate {
|
|||
/**
|
||||
* Promise to wait for handlers to be initialized.
|
||||
*/
|
||||
protected handlersInitPromise: Promise<any>;
|
||||
protected handlersInitPromise: Promise<void>;
|
||||
|
||||
/**
|
||||
* Function to resolve the handlers init promise.
|
||||
|
@ -136,7 +136,7 @@ export class CoreDelegate {
|
|||
* @param params Parameters to pass to the function.
|
||||
* @return Function returned value or default value.
|
||||
*/
|
||||
private execute(handler: any, fnName: string, params?: any[]): any {
|
||||
private execute(handler: CoreDelegateHandler, fnName: string, params?: any[]): any {
|
||||
if (handler && handler[fnName]) {
|
||||
return handler[fnName].apply(handler, params);
|
||||
} else if (this.defaultHandler && this.defaultHandler[fnName]) {
|
||||
|
@ -243,7 +243,7 @@ export class CoreDelegate {
|
|||
protected updateHandler(handler: CoreDelegateHandler, time: number): Promise<void> {
|
||||
const siteId = CoreSites.instance.getCurrentSiteId();
|
||||
const currentSite = CoreSites.instance.getCurrentSite();
|
||||
let promise;
|
||||
let promise: Promise<boolean>;
|
||||
|
||||
if (this.updatePromises[siteId] && this.updatePromises[siteId][handler.name]) {
|
||||
// There's already an update ongoing for this handler, return the promise.
|
||||
|
@ -252,18 +252,14 @@ export class CoreDelegate {
|
|||
this.updatePromises[siteId] = {};
|
||||
}
|
||||
|
||||
if (!CoreSites.instance.isLoggedIn()) {
|
||||
promise = Promise.reject(null);
|
||||
} else if (this.isFeatureDisabled(handler, currentSite)) {
|
||||
if (!CoreSites.instance.isLoggedIn() || this.isFeatureDisabled(handler, currentSite)) {
|
||||
promise = Promise.resolve(false);
|
||||
} else {
|
||||
promise = Promise.resolve(handler.isEnabled());
|
||||
promise = handler.isEnabled().catch(() => false);
|
||||
}
|
||||
|
||||
// Checks if the handler is enabled.
|
||||
this.updatePromises[siteId][handler.name] = promise.catch(() => {
|
||||
return false;
|
||||
}).then((enabled: boolean) => {
|
||||
this.updatePromises[siteId][handler.name] = promise.then((enabled: boolean) => {
|
||||
// Check that site hasn't changed since the check started.
|
||||
if (CoreSites.instance.getCurrentSiteId() === siteId) {
|
||||
const key = handler[this.handlerNameProperty] || handler.name;
|
||||
|
@ -298,9 +294,9 @@ export class CoreDelegate {
|
|||
*
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected updateHandlers(): Promise<void> {
|
||||
const promises = [],
|
||||
now = Date.now();
|
||||
protected async updateHandlers(): Promise<void> {
|
||||
const promises = [];
|
||||
const now = Date.now();
|
||||
|
||||
this.logger.debug('Updating handlers for current site.');
|
||||
|
||||
|
@ -311,21 +307,19 @@ export class CoreDelegate {
|
|||
promises.push(this.updateHandler(this.handlers[name], now));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return true;
|
||||
}, () => {
|
||||
// Never reject.
|
||||
return true;
|
||||
}).then(() => {
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
} catch (e) {
|
||||
// Never reject
|
||||
}
|
||||
|
||||
// Verify that this call is the last one that was started.
|
||||
if (this.isLastUpdateCall(now)) {
|
||||
this.handlersInitialized = true;
|
||||
this.handlersInitResolve();
|
||||
// Verify that this call is the last one that was started.
|
||||
if (this.isLastUpdateCall(now)) {
|
||||
this.handlersInitialized = true;
|
||||
this.handlersInitResolve();
|
||||
|
||||
this.updateData();
|
||||
}
|
||||
});
|
||||
this.updateData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -335,6 +329,7 @@ export class CoreDelegate {
|
|||
updateData(): any {
|
||||
// To be overridden.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface CoreDelegateHandler {
|
||||
|
@ -346,7 +341,8 @@ export interface CoreDelegateHandler {
|
|||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean>;
|
||||
isEnabled(): Promise<boolean>;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export class CoreInterceptor implements HttpInterceptor {
|
|||
*/
|
||||
static serialize(obj: any, addNull?: boolean): string {
|
||||
let query = '';
|
||||
let fullSubName;
|
||||
let fullSubName: string;
|
||||
let subValue;
|
||||
let innerObj;
|
||||
|
||||
|
@ -68,10 +68,11 @@ export class CoreInterceptor implements HttpInterceptor {
|
|||
const newReq = req.clone({
|
||||
headers: req.headers.set('Content-Type', 'application/x-www-form-urlencoded'),
|
||||
body: typeof req.body == 'object' && String(req.body) != '[object File]' ?
|
||||
CoreInterceptor.serialize(req.body) : req.body
|
||||
CoreInterceptor.serialize(req.body) : req.body,
|
||||
});
|
||||
|
||||
// Pass on the cloned request instead of the original request.
|
||||
return next.handle(newReq);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -92,7 +92,8 @@ export class CoreNativeToAngularHttpResponse<T> extends AngularHttpResponse<T> {
|
|||
headers: new HttpHeaders(nativeResponse.headers),
|
||||
status: nativeResponse.status,
|
||||
statusText: HTTP_STATUS_MESSAGES[nativeResponse.status] || '',
|
||||
url: nativeResponse.url || ''
|
||||
url: nativeResponse.url || '',
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ export type CoreQueueRunnerAddOptions = {
|
|||
* A queue to prevent having too many concurrent executions.
|
||||
*/
|
||||
export class CoreQueueRunner {
|
||||
|
||||
protected queue: {[id: string]: CoreQueueRunnerItem} = {};
|
||||
protected orderedQueue: CoreQueueRunnerItem[] = [];
|
||||
protected numberRunning = 0;
|
||||
|
@ -140,4 +141,5 @@ export class CoreQueueRunner {
|
|||
|
||||
return item.deferred.promise;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -80,4 +80,5 @@ export class CoreSingletonsFactory {
|
|||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -131,6 +131,7 @@ export interface SQLiteDBForeignKeySchema {
|
|||
* this.db = new SQLiteDB('MyDB');
|
||||
*/
|
||||
export class SQLiteDB {
|
||||
|
||||
db: SQLiteObject;
|
||||
promise: Promise<void>;
|
||||
|
||||
|
@ -240,10 +241,10 @@ export class SQLiteDB {
|
|||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async close(): Promise<any> {
|
||||
async close(): Promise<void> {
|
||||
await this.ready();
|
||||
|
||||
return this.db.close();
|
||||
await this.db.close();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -253,7 +254,7 @@ export class SQLiteDB {
|
|||
* @param conditions The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return Promise resolved with the count of records returned from the specified criteria.
|
||||
*/
|
||||
countRecords(table: string, conditions?: object): Promise<number> {
|
||||
async countRecords(table: string, conditions?: SQLiteDBRecordValues): Promise<number> {
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
||||
return this.countRecordsSelect(table, selectAndParams[0], selectAndParams[1]);
|
||||
|
@ -268,7 +269,8 @@ export class SQLiteDB {
|
|||
* @param countItem The count string to be used in the SQL call. Default is COUNT('x').
|
||||
* @return Promise resolved with the count of records returned from the specified criteria.
|
||||
*/
|
||||
countRecordsSelect(table: string, select: string = '', params?: any, countItem: string = 'COUNT(\'x\')'): Promise<number> {
|
||||
async countRecordsSelect(table: string, select: string = '', params?: SQLiteDBRecordValue[],
|
||||
countItem: string = 'COUNT(\'x\')'): Promise<number> {
|
||||
if (select) {
|
||||
select = 'WHERE ' + select;
|
||||
}
|
||||
|
@ -285,14 +287,13 @@ export class SQLiteDB {
|
|||
* @param params An array of sql parameters.
|
||||
* @return Promise resolved with the count.
|
||||
*/
|
||||
countRecordsSql(sql: string, params?: any): Promise<number> {
|
||||
return this.getFieldSql(sql, params).then((count) => {
|
||||
if (typeof count != 'number' || count < 0) {
|
||||
return 0;
|
||||
}
|
||||
async countRecordsSql(sql: string, params?: SQLiteDBRecordValue[]): Promise<number> {
|
||||
const count = await this.getFieldSql(sql, params);
|
||||
if (typeof count != 'number' || count < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -306,11 +307,11 @@ export class SQLiteDB {
|
|||
* @param tableCheck Check constraint for the table.
|
||||
* @return Promise resolved when success.
|
||||
*/
|
||||
createTable(name: string, columns: SQLiteDBColumnSchema[], primaryKeys?: string[], uniqueKeys?: string[][],
|
||||
foreignKeys?: SQLiteDBForeignKeySchema[], tableCheck?: string): Promise<any> {
|
||||
async createTable(name: string, columns: SQLiteDBColumnSchema[], primaryKeys?: string[], uniqueKeys?: string[][],
|
||||
foreignKeys?: SQLiteDBForeignKeySchema[], tableCheck?: string): Promise<void> {
|
||||
const sql = this.buildCreateTableSql(name, columns, primaryKeys, uniqueKeys, foreignKeys, tableCheck);
|
||||
|
||||
return this.execute(sql);
|
||||
await this.execute(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -319,9 +320,8 @@ export class SQLiteDB {
|
|||
* @param table Table schema.
|
||||
* @return Promise resolved when success.
|
||||
*/
|
||||
createTableFromSchema(table: SQLiteDBTableSchema): Promise<any> {
|
||||
return this.createTable(table.name, table.columns, table.primaryKeys, table.uniqueKeys,
|
||||
table.foreignKeys, table.tableCheck);
|
||||
async createTableFromSchema(table: SQLiteDBTableSchema): Promise<void> {
|
||||
await this.createTable(table.name, table.columns, table.primaryKeys, table.uniqueKeys, table.foreignKeys, table.tableCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -330,13 +330,13 @@ export class SQLiteDB {
|
|||
* @param tables List of table schema.
|
||||
* @return Promise resolved when success.
|
||||
*/
|
||||
createTablesFromSchema(tables: SQLiteDBTableSchema[]): Promise<any> {
|
||||
async createTablesFromSchema(tables: SQLiteDBTableSchema[]): Promise<void> {
|
||||
const promises = [];
|
||||
tables.forEach((table) => {
|
||||
promises.push(this.createTableFromSchema(table));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -345,12 +345,14 @@ export class SQLiteDB {
|
|||
*
|
||||
* @param table The table to delete from.
|
||||
* @param conditions The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return Promise resolved when done.
|
||||
* @return Promise resolved with the number of affected rows.
|
||||
*/
|
||||
deleteRecords(table: string, conditions?: object): Promise<any> {
|
||||
async deleteRecords(table: string, conditions?: SQLiteDBRecordValues): Promise<number> {
|
||||
if (conditions === null || typeof conditions == 'undefined') {
|
||||
// No conditions, delete the whole table.
|
||||
return this.execute(`DELETE FROM ${table}`);
|
||||
const result = await this.execute(`DELETE FROM ${table}`);
|
||||
|
||||
return result.rowsAffected;
|
||||
}
|
||||
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
@ -364,9 +366,9 @@ export class SQLiteDB {
|
|||
* @param table The table to delete from.
|
||||
* @param field The name of a field.
|
||||
* @param values The values field might take.
|
||||
* @return Promise resolved when done.
|
||||
* @return Promise resolved with the number of affected rows.
|
||||
*/
|
||||
deleteRecordsList(table: string, field: string, values: any[]): Promise<any> {
|
||||
async deleteRecordsList(table: string, field: string, values: SQLiteDBRecordValue[]): Promise<number> {
|
||||
const selectAndParams = this.whereClauseList(field, values);
|
||||
|
||||
return this.deleteRecordsSelect(table, selectAndParams[0], selectAndParams[1]);
|
||||
|
@ -378,14 +380,16 @@ export class SQLiteDB {
|
|||
* @param table The table to delete from.
|
||||
* @param select A fragment of SQL to be used in a where clause in the SQL call.
|
||||
* @param params Array of sql parameters.
|
||||
* @return Promise resolved when done.
|
||||
* @return Promise resolved with the number of affected rows.
|
||||
*/
|
||||
deleteRecordsSelect(table: string, select: string = '', params?: any[]): Promise<any> {
|
||||
async deleteRecordsSelect(table: string, select: string = '', params?: SQLiteDBRecordValue[]): Promise<number> {
|
||||
if (select) {
|
||||
select = 'WHERE ' + select;
|
||||
}
|
||||
|
||||
return this.execute(`DELETE FROM ${table} ${select}`, params);
|
||||
const result = await this.execute(`DELETE FROM ${table} ${select}`, params);
|
||||
|
||||
return result.rowsAffected;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -394,8 +398,8 @@ export class SQLiteDB {
|
|||
* @param name The table name.
|
||||
* @return Promise resolved when success.
|
||||
*/
|
||||
dropTable(name: string): Promise<any> {
|
||||
return this.execute(`DROP TABLE IF EXISTS ${name}`);
|
||||
async dropTable(name: string): Promise<void> {
|
||||
await this.execute(`DROP TABLE IF EXISTS ${name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -407,7 +411,7 @@ export class SQLiteDB {
|
|||
* @param params Query parameters.
|
||||
* @return Promise resolved with the result.
|
||||
*/
|
||||
async execute(sql: string, params?: any[]): Promise<any> {
|
||||
async execute(sql: string, params?: SQLiteDBRecordValue[]): Promise<any> {
|
||||
await this.ready();
|
||||
|
||||
return this.db.executeSql(sql, params);
|
||||
|
@ -421,10 +425,10 @@ export class SQLiteDB {
|
|||
* @param sqlStatements SQL statements to execute.
|
||||
* @return Promise resolved with the result.
|
||||
*/
|
||||
async executeBatch(sqlStatements: any[]): Promise<any> {
|
||||
async executeBatch(sqlStatements: (string | SQLiteDBRecordValue[])[][]): Promise<void> {
|
||||
await this.ready();
|
||||
|
||||
return this.db.sqlBatch(sqlStatements);
|
||||
await this.db.sqlBatch(sqlStatements);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -432,27 +436,35 @@ export class SQLiteDB {
|
|||
*
|
||||
* @param data Data to insert.
|
||||
*/
|
||||
protected formatDataToInsert(data: object): void {
|
||||
protected formatDataToInsert(data: SQLiteDBRecordValues): void {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove undefined entries and convert null to "NULL".
|
||||
for (const name in data) {
|
||||
const value = data[name];
|
||||
if (typeof value == 'undefined') {
|
||||
if (typeof data[name] == 'undefined') {
|
||||
delete data[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the data to where params.
|
||||
*
|
||||
* @param data Object data.
|
||||
*/
|
||||
protected formatDataToSQLParams(data: SQLiteDBRecordValues): SQLiteDBRecordValue[] {
|
||||
return Object.keys(data).map((key) => data[key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the records from a table.
|
||||
*
|
||||
* @param table The table to query.
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
getAllRecords(table: string): Promise<any> {
|
||||
async getAllRecords(table: string): Promise<SQLiteDBRecordValues[]> {
|
||||
return this.getRecords(table);
|
||||
}
|
||||
|
||||
|
@ -464,7 +476,7 @@ export class SQLiteDB {
|
|||
* @param conditions The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return Promise resolved with the field's value.
|
||||
*/
|
||||
getField(table: string, field: string, conditions?: object): Promise<any> {
|
||||
async getField(table: string, field: string, conditions?: SQLiteDBRecordValues): Promise<SQLiteDBRecordValue> {
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
||||
return this.getFieldSelect(table, field, selectAndParams[0], selectAndParams[1]);
|
||||
|
@ -479,7 +491,8 @@ export class SQLiteDB {
|
|||
* @param params Array of sql parameters.
|
||||
* @return Promise resolved with the field's value.
|
||||
*/
|
||||
getFieldSelect(table: string, field: string, select: string = '', params?: any[]): Promise<any> {
|
||||
async getFieldSelect(table: string, field: string, select: string = '', params?: SQLiteDBRecordValue[]):
|
||||
Promise<SQLiteDBRecordValue> {
|
||||
if (select) {
|
||||
select = 'WHERE ' + select;
|
||||
}
|
||||
|
@ -494,10 +507,10 @@ export class SQLiteDB {
|
|||
* @param params An array of sql parameters.
|
||||
* @return Promise resolved with the field's value.
|
||||
*/
|
||||
async getFieldSql(sql: string, params?: any[]): Promise<any> {
|
||||
async getFieldSql(sql: string, params?: SQLiteDBRecordValue[]): Promise<SQLiteDBRecordValue> {
|
||||
const record = await this.getRecordSql(sql, params);
|
||||
if (!record) {
|
||||
return Promise.reject(null);
|
||||
throw null;
|
||||
}
|
||||
|
||||
return record[Object.keys(record)[0]];
|
||||
|
@ -512,17 +525,14 @@ export class SQLiteDB {
|
|||
* meaning return empty. Other values will become part of the returned SQL fragment.
|
||||
* @return A list containing the constructed sql fragment and an array of parameters.
|
||||
*/
|
||||
getInOrEqual(items: any, equal: boolean = true, onEmptyItems?: any): any[] {
|
||||
let sql;
|
||||
let params;
|
||||
|
||||
if (typeof onEmptyItems == 'undefined') {
|
||||
onEmptyItems = false;
|
||||
}
|
||||
getInOrEqual(items: SQLiteDBRecordValue | SQLiteDBRecordValue[], equal: boolean = true, onEmptyItems?: SQLiteDBRecordValue):
|
||||
SQLiteDBQueryParams {
|
||||
let sql = '';
|
||||
let params: SQLiteDBRecordValue[];
|
||||
|
||||
// Default behavior, return empty data on empty array.
|
||||
if (Array.isArray(items) && !items.length && onEmptyItems === false) {
|
||||
return ['', []];
|
||||
if (Array.isArray(items) && !items.length && typeof onEmptyItems == 'undefined') {
|
||||
return { sql: '', params: [] };
|
||||
}
|
||||
|
||||
// Handle onEmptyItems on empty array of items.
|
||||
|
@ -530,7 +540,7 @@ export class SQLiteDB {
|
|||
if (onEmptyItems === null) { // Special case, NULL value.
|
||||
sql = equal ? ' IS NULL' : ' IS NOT NULL';
|
||||
|
||||
return [sql, []];
|
||||
return { sql, params: [] };
|
||||
} else {
|
||||
items = [onEmptyItems]; // Rest of cases, prepare items for processing.
|
||||
}
|
||||
|
@ -544,7 +554,7 @@ export class SQLiteDB {
|
|||
params = items;
|
||||
}
|
||||
|
||||
return [sql, params];
|
||||
return { sql, params };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -564,7 +574,7 @@ export class SQLiteDB {
|
|||
* @param fields A comma separated list of fields to return.
|
||||
* @return Promise resolved with the record, rejected if not found.
|
||||
*/
|
||||
getRecord(table: string, conditions?: object, fields: string = '*'): Promise<any> {
|
||||
getRecord(table: string, conditions?: SQLiteDBRecordValues, fields: string = '*'): Promise<SQLiteDBRecordValues> {
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
||||
return this.getRecordSelect(table, selectAndParams[0], selectAndParams[1], fields);
|
||||
|
@ -579,7 +589,8 @@ export class SQLiteDB {
|
|||
* @param fields A comma separated list of fields to return.
|
||||
* @return Promise resolved with the record, rejected if not found.
|
||||
*/
|
||||
getRecordSelect(table: string, select: string = '', params: any[] = [], fields: string = '*'): Promise<any> {
|
||||
getRecordSelect(table: string, select: string = '', params: SQLiteDBRecordValue[] = [], fields: string = '*'):
|
||||
Promise<SQLiteDBRecordValues> {
|
||||
if (select) {
|
||||
select = ' WHERE ' + select;
|
||||
}
|
||||
|
@ -597,11 +608,11 @@ export class SQLiteDB {
|
|||
* @param params List of sql parameters
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
async getRecordSql(sql: string, params?: any[]): Promise<any> {
|
||||
async getRecordSql(sql: string, params?: SQLiteDBRecordValue[]): Promise<SQLiteDBRecordValues> {
|
||||
const result = await this.getRecordsSql(sql, params, 0, 1);
|
||||
if (!result || !result.length) {
|
||||
// Not found, reject.
|
||||
return Promise.reject(null);
|
||||
throw null;
|
||||
}
|
||||
|
||||
return result[0];
|
||||
|
@ -618,8 +629,8 @@ export class SQLiteDB {
|
|||
* @param limitNum Return a subset comprising this many records in total.
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
getRecords(table: string, conditions?: object, sort: string = '', fields: string = '*', limitFrom: number = 0,
|
||||
limitNum: number = 0): Promise<any> {
|
||||
getRecords(table: string, conditions?: SQLiteDBRecordValues, sort: string = '', fields: string = '*', limitFrom: number = 0,
|
||||
limitNum: number = 0): Promise<SQLiteDBRecordValues[]> {
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
|
||||
return this.getRecordsSelect(table, selectAndParams[0], selectAndParams[1], sort, fields, limitFrom, limitNum);
|
||||
|
@ -637,8 +648,8 @@ export class SQLiteDB {
|
|||
* @param limitNum Return a subset comprising this many records in total.
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
getRecordsList(table: string, field: string, values: any[], sort: string = '', fields: string = '*', limitFrom: number = 0,
|
||||
limitNum: number = 0): Promise<any> {
|
||||
getRecordsList(table: string, field: string, values: SQLiteDBRecordValue[], sort: string = '', fields: string = '*',
|
||||
limitFrom: number = 0, limitNum: number = 0): Promise<SQLiteDBRecordValues[]> {
|
||||
const selectAndParams = this.whereClauseList(field, values);
|
||||
|
||||
return this.getRecordsSelect(table, selectAndParams[0], selectAndParams[1], sort, fields, limitFrom, limitNum);
|
||||
|
@ -656,8 +667,8 @@ export class SQLiteDB {
|
|||
* @param limitNum Return a subset comprising this many records in total.
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
getRecordsSelect(table: string, select: string = '', params: any[] = [], sort: string = '', fields: string = '*',
|
||||
limitFrom: number = 0, limitNum: number = 0): Promise<any> {
|
||||
getRecordsSelect(table: string, select: string = '', params: SQLiteDBRecordValue[] = [], sort: string = '',
|
||||
fields: string = '*', limitFrom: number = 0, limitNum: number = 0): Promise<SQLiteDBRecordValues[]> {
|
||||
if (select) {
|
||||
select = ' WHERE ' + select;
|
||||
}
|
||||
|
@ -679,7 +690,8 @@ export class SQLiteDB {
|
|||
* @param limitNum Return a subset comprising this many records.
|
||||
* @return Promise resolved with the records.
|
||||
*/
|
||||
async getRecordsSql(sql: string, params?: any[], limitFrom?: number, limitNum?: number): Promise<any> {
|
||||
async getRecordsSql(sql: string, params?: SQLiteDBRecordValue[], limitFrom?: number, limitNum?: number):
|
||||
Promise<SQLiteDBRecordValues[]> {
|
||||
const limits = this.normaliseLimitFromNum(limitFrom, limitNum);
|
||||
|
||||
if (limits[0] || limits[1]) {
|
||||
|
@ -706,31 +718,31 @@ export class SQLiteDB {
|
|||
* @param data A data object with values for one or more fields in the record.
|
||||
* @return Array with the SQL query and the params.
|
||||
*/
|
||||
protected getSqlInsertQuery(table: string, data: object): any[] {
|
||||
protected getSqlInsertQuery(table: string, data: SQLiteDBRecordValues): SQLiteDBQueryParams {
|
||||
this.formatDataToInsert(data);
|
||||
|
||||
const keys = Object.keys(data);
|
||||
const fields = keys.join(',');
|
||||
const questionMarks = ',?'.repeat(keys.length).substr(1);
|
||||
|
||||
return [
|
||||
`INSERT OR REPLACE INTO ${table} (${fields}) VALUES (${questionMarks})`,
|
||||
keys.map((key) => data[key])
|
||||
];
|
||||
return {
|
||||
sql: `INSERT OR REPLACE INTO ${table} (${fields}) VALUES (${questionMarks})`,
|
||||
params: this.formatDataToSQLParams(data),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the database.
|
||||
*/
|
||||
init(): void {
|
||||
this.promise = Platform.instance.ready().then(() => {
|
||||
return SQLite.instance.create({
|
||||
this.promise = Platform.instance.ready()
|
||||
.then(() => SQLite.instance.create({
|
||||
name: this.name,
|
||||
location: 'default'
|
||||
location: 'default',
|
||||
}))
|
||||
.then((db: SQLiteObject) => {
|
||||
this.db = db;
|
||||
});
|
||||
}).then((db: SQLiteObject) => {
|
||||
this.db = db;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -740,7 +752,7 @@ export class SQLiteDB {
|
|||
* @param data A data object with values for one or more fields in the record.
|
||||
* @return Promise resolved with new rowId. Please notice this rowId is internal from SQLite.
|
||||
*/
|
||||
async insertRecord(table: string, data: object): Promise<number> {
|
||||
async insertRecord(table: string, data: SQLiteDBRecordValues): Promise<number> {
|
||||
const sqlAndParams = this.getSqlInsertQuery(table, data);
|
||||
const result = await this.execute(sqlAndParams[0], sqlAndParams[1]);
|
||||
|
||||
|
@ -754,18 +766,18 @@ export class SQLiteDB {
|
|||
* @param dataObjects List of objects to be inserted.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
insertRecords(table: string, dataObjects: object[]): Promise<any> {
|
||||
async insertRecords(table: string, dataObjects: SQLiteDBRecordValues[]): Promise<void> {
|
||||
if (!Array.isArray(dataObjects)) {
|
||||
return Promise.reject(null);
|
||||
throw null;
|
||||
}
|
||||
|
||||
const statements = [];
|
||||
const statements = dataObjects.map((dataObject) => {
|
||||
const statement = this.getSqlInsertQuery(table, dataObject);
|
||||
|
||||
dataObjects.forEach((dataObject) => {
|
||||
statements.push(this.getSqlInsertQuery(table, dataObject));
|
||||
return [statement.sql, statement.params];
|
||||
});
|
||||
|
||||
return this.executeBatch(statements);
|
||||
await this.executeBatch(statements);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -777,12 +789,13 @@ export class SQLiteDB {
|
|||
* @param fields A comma separated list of fields to return.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
insertRecordsFrom(table: string, source: string, conditions?: object, fields: string = '*'): Promise<any> {
|
||||
async insertRecordsFrom(table: string, source: string, conditions?: SQLiteDBRecordValues, fields: string = '*'):
|
||||
Promise<void> {
|
||||
const selectAndParams = this.whereClause(conditions);
|
||||
const select = selectAndParams[0] ? 'WHERE ' + selectAndParams[0] : '';
|
||||
const params = selectAndParams[1];
|
||||
|
||||
return this.execute(`INSERT INTO ${table} SELECT ${fields} FROM ${source} ${select}`, params);
|
||||
await this.execute(`INSERT INTO ${table} SELECT ${fields} FROM ${source} ${select}`, params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -794,19 +807,19 @@ export class SQLiteDB {
|
|||
* @param limitNum How many results to return.
|
||||
* @return Normalised limit params in array: [limitFrom, limitNum].
|
||||
*/
|
||||
normaliseLimitFromNum(limitFrom: any, limitNum: any): number[] {
|
||||
normaliseLimitFromNum(limitFrom: number, limitNum: number): number[] {
|
||||
// We explicilty treat these cases as 0.
|
||||
if (typeof limitFrom == 'undefined' || limitFrom === null || limitFrom === '' || limitFrom === -1) {
|
||||
if (!limitFrom || limitFrom === -1) {
|
||||
limitFrom = 0;
|
||||
}
|
||||
if (typeof limitNum == 'undefined' || limitNum === null || limitNum === '' || limitNum === -1) {
|
||||
limitNum = 0;
|
||||
} else {
|
||||
limitFrom = Math.max(0, limitFrom);
|
||||
}
|
||||
|
||||
limitFrom = parseInt(limitFrom, 10);
|
||||
limitNum = parseInt(limitNum, 10);
|
||||
limitFrom = Math.max(0, limitFrom);
|
||||
limitNum = Math.max(0, limitNum);
|
||||
if (!limitNum || limitNum === -1) {
|
||||
limitNum = 0;
|
||||
} else {
|
||||
limitNum = Math.max(0, limitNum);
|
||||
}
|
||||
|
||||
return [limitFrom, limitNum];
|
||||
}
|
||||
|
@ -816,10 +829,10 @@ export class SQLiteDB {
|
|||
*
|
||||
* @return Promise resolved when open.
|
||||
*/
|
||||
async open(): Promise<any> {
|
||||
async open(): Promise<void> {
|
||||
await this.ready();
|
||||
|
||||
return this.db.open();
|
||||
await this.db.open();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -838,10 +851,10 @@ export class SQLiteDB {
|
|||
* @param conditions The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return Promise resolved if exists, rejected otherwise.
|
||||
*/
|
||||
async recordExists(table: string, conditions?: object): Promise<void> {
|
||||
async recordExists(table: string, conditions?: SQLiteDBRecordValues): Promise<void> {
|
||||
const record = await this.getRecord(table, conditions);
|
||||
if (!record) {
|
||||
return Promise.reject(null);
|
||||
throw null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -853,10 +866,10 @@ export class SQLiteDB {
|
|||
* @param params An array of sql parameters.
|
||||
* @return Promise resolved if exists, rejected otherwise.
|
||||
*/
|
||||
async recordExistsSelect(table: string, select: string = '', params: any[] = []): Promise<void> {
|
||||
async recordExistsSelect(table: string, select: string = '', params: SQLiteDBRecordValue[] = []): Promise<void> {
|
||||
const record = await this.getRecordSelect(table, select, params);
|
||||
if (!record) {
|
||||
return Promise.reject(null);
|
||||
throw null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -867,10 +880,10 @@ export class SQLiteDB {
|
|||
* @param params An array of sql parameters.
|
||||
* @return Promise resolved if exists, rejected otherwise.
|
||||
*/
|
||||
async recordExistsSql(sql: string, params?: any[]): Promise<void> {
|
||||
async recordExistsSql(sql: string, params?: SQLiteDBRecordValue[]): Promise<void> {
|
||||
const record = await this.getRecordSql(sql, params);
|
||||
if (!record) {
|
||||
return Promise.reject(null);
|
||||
throw null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -881,7 +894,8 @@ export class SQLiteDB {
|
|||
* @return Promise resolved if exists, rejected otherwise.
|
||||
*/
|
||||
async tableExists(name: string): Promise<void> {
|
||||
await this.recordExists('sqlite_master', {type: 'table', tbl_name: name});
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
await this.recordExists('sqlite_master', { type: 'table', tbl_name: name });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -890,31 +904,12 @@ export class SQLiteDB {
|
|||
* @param string table The database table to update.
|
||||
* @param data An object with the fields to update: fieldname=>fieldvalue.
|
||||
* @param conditions The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return Promise resolved when updated.
|
||||
* @return Promise resolved with the number of affected rows.
|
||||
*/
|
||||
updateRecords(table: string, data: any, conditions?: any): Promise<any> {
|
||||
|
||||
this.formatDataToInsert(data);
|
||||
|
||||
if (!data || !Object.keys(data).length) {
|
||||
// No fields to update, consider it's done.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async updateRecords(table: string, data: SQLiteDBRecordValues, conditions?: SQLiteDBRecordValues): Promise<number> {
|
||||
const whereAndParams = this.whereClause(conditions);
|
||||
const sets = [];
|
||||
let sql;
|
||||
let params;
|
||||
|
||||
for (const key in data) {
|
||||
sets.push(`${key} = ?`);
|
||||
}
|
||||
|
||||
sql = `UPDATE ${table} SET ${sets.join(', ')} WHERE ${whereAndParams[0]}`;
|
||||
// Create the list of params using the "data" object and the params for the where clause.
|
||||
params = Object.keys(data).map((key) => data[key]).concat(whereAndParams[1]);
|
||||
|
||||
return this.execute(sql, params);
|
||||
return this.updateRecordsWhere(table, data, whereAndParams[0], whereAndParams[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -924,34 +919,35 @@ export class SQLiteDB {
|
|||
* @param data An object with the fields to update: fieldname=>fieldvalue.
|
||||
* @param where Where clause. Must not include the "WHERE" word.
|
||||
* @param whereParams Params for the where clause.
|
||||
* @return Promise resolved when updated.
|
||||
* @return Promise resolved with the number of affected rows.
|
||||
*/
|
||||
updateRecordsWhere(table: string, data: any, where?: string, whereParams?: any[]): Promise<any> {
|
||||
async updateRecordsWhere(table: string, data: SQLiteDBRecordValues, where?: string, whereParams?: SQLiteDBRecordValue[]):
|
||||
Promise<number> {
|
||||
this.formatDataToInsert(data);
|
||||
if (!data || !Object.keys(data).length) {
|
||||
// No fields to update, consider it's done.
|
||||
return Promise.resolve();
|
||||
return 0;
|
||||
}
|
||||
|
||||
const sets = [];
|
||||
let sql;
|
||||
let params;
|
||||
|
||||
for (const key in data) {
|
||||
sets.push(`${key} = ?`);
|
||||
}
|
||||
|
||||
sql = `UPDATE ${table} SET ${sets.join(', ')}`;
|
||||
let sql = `UPDATE ${table} SET ${sets.join(', ')}`;
|
||||
if (where) {
|
||||
sql += ` WHERE ${where}`;
|
||||
}
|
||||
|
||||
// Create the list of params using the "data" object and the params for the where clause.
|
||||
params = Object.keys(data).map((key) => data[key]);
|
||||
let params = this.formatDataToSQLParams(data);
|
||||
if (where && whereParams) {
|
||||
params = params.concat(whereParams);
|
||||
}
|
||||
|
||||
return this.execute(sql, params);
|
||||
const result = await this.execute(sql, params);
|
||||
|
||||
return result.rowsAffected;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -960,9 +956,12 @@ export class SQLiteDB {
|
|||
* @param conditions The conditions to build the where clause. Must not contain numeric indexes.
|
||||
* @return An array list containing sql 'where' part and 'params'.
|
||||
*/
|
||||
whereClause(conditions: any = {}): any[] {
|
||||
whereClause(conditions: SQLiteDBRecordValues = {}): SQLiteDBQueryParams {
|
||||
if (!conditions || !Object.keys(conditions).length) {
|
||||
return ['1 = 1', []];
|
||||
return {
|
||||
sql: '1 = 1',
|
||||
params: [],
|
||||
};
|
||||
}
|
||||
|
||||
const where = [];
|
||||
|
@ -979,7 +978,10 @@ export class SQLiteDB {
|
|||
}
|
||||
}
|
||||
|
||||
return [where.join(' AND '), params];
|
||||
return {
|
||||
sql: where.join(' AND '),
|
||||
params,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -989,39 +991,50 @@ export class SQLiteDB {
|
|||
* @param values The values field might take.
|
||||
* @return An array containing sql 'where' part and 'params'.
|
||||
*/
|
||||
whereClauseList(field: string, values: any[]): any[] {
|
||||
whereClauseList(field: string, values: SQLiteDBRecordValue[]): SQLiteDBQueryParams {
|
||||
if (!values || !values.length) {
|
||||
return ['1 = 2', []]; // Fake condition, won't return rows ever.
|
||||
return {
|
||||
sql: '1 = 2', // Fake condition, won't return rows ever.
|
||||
params: [],
|
||||
};
|
||||
}
|
||||
|
||||
const params = [];
|
||||
let select = '';
|
||||
let sql = '';
|
||||
|
||||
values.forEach((value) => {
|
||||
if (typeof value == 'boolean') {
|
||||
value = Number(value);
|
||||
}
|
||||
|
||||
if (typeof value == 'undefined' || value === null) {
|
||||
select = field + ' IS NULL';
|
||||
sql = field + ' IS NULL';
|
||||
} else {
|
||||
params.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
if (params && params.length) {
|
||||
if (select !== '') {
|
||||
select = select + ' OR ';
|
||||
if (sql !== '') {
|
||||
sql = sql + ' OR ';
|
||||
}
|
||||
|
||||
if (params.length == 1) {
|
||||
select = select + field + ' = ?';
|
||||
sql = sql + field + ' = ?';
|
||||
} else {
|
||||
const questionMarks = ',?'.repeat(params.length).substr(1);
|
||||
select = select + field + ' IN (' + questionMarks + ')';
|
||||
sql = sql + field + ' IN (' + questionMarks + ')';
|
||||
}
|
||||
}
|
||||
|
||||
return [select, params];
|
||||
return { sql, params };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type SQLiteDBRecordValues = {
|
||||
[key in string ]: SQLiteDBRecordValue;
|
||||
};
|
||||
|
||||
export type SQLiteDBQueryParams = {
|
||||
sql: string;
|
||||
params: SQLiteDBRecordValue[];
|
||||
};
|
||||
|
||||
type SQLiteDBRecordValue = number | string;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
import { CoreIconComponent } from './icon/icon';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CoreIconComponent,
|
||||
],
|
||||
imports: [],
|
||||
exports: [
|
||||
CoreIconComponent,
|
||||
]
|
||||
})
|
||||
export class CoreComponentsModule {}
|
|
@ -0,0 +1 @@
|
|||
<div></div>
|
|
@ -0,0 +1,52 @@
|
|||
// TODO ionic 5
|
||||
:host-context([dir=rtl]) ion-icon {
|
||||
&.core-icon-dir-flip,
|
||||
&.fa-caret-right,
|
||||
&.ion-md-send, &.ion-ios-send {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Slash
|
||||
@font-face {
|
||||
font-family: "Moodle Slash Icon";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/assets/fonts/slash-icon.woff") format("woff");
|
||||
}
|
||||
|
||||
:host {
|
||||
&.fa {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
// Center font awesome icons
|
||||
&.fa::before {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.icon-slash {
|
||||
position: relative;
|
||||
&::after {
|
||||
content: "/";
|
||||
font-family: "Moodle Slash Icon";
|
||||
font-size: 0.75em;
|
||||
margin-top: 0.125em;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
color: var(--ion-color-danger);
|
||||
}
|
||||
|
||||
&.fa::after {
|
||||
font-size: 1em;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
// (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 { Component, Input, OnChanges, OnDestroy, ElementRef, SimpleChange } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Core Icon is a component that enables the posibility to add fontawesome icon to the html. It's recommended if both fontawesome
|
||||
* or ionicons can be used in the name attribute. To use fontawesome just place the full icon name with the fa- prefix and
|
||||
* the component will detect it.
|
||||
* Check available icons at https://fontawesome.com/v4.7.0/icons/.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-icon',
|
||||
templateUrl: 'core-icon.html',
|
||||
styleUrls: ['icon.scss'],
|
||||
})
|
||||
export class CoreIconComponent implements OnChanges, OnDestroy {
|
||||
// Common params.
|
||||
@Input() name: string;
|
||||
@Input('color') color?: string;
|
||||
@Input('slash') slash?: boolean; // Display a red slash over the icon.
|
||||
|
||||
// Ionicons params.
|
||||
@Input('isActive') isActive?: boolean;
|
||||
@Input('md') md?: string;
|
||||
@Input('ios') ios?: string;
|
||||
|
||||
// FontAwesome params.
|
||||
@Input('fixed-width') fixedWidth: string;
|
||||
|
||||
@Input('label') ariaLabel?: string;
|
||||
@Input() flipRtl?: boolean; // Whether to flip the icon in RTL. Defaults to false.
|
||||
|
||||
protected element: HTMLElement;
|
||||
protected newElement: HTMLElement;
|
||||
|
||||
constructor(el: ElementRef) {
|
||||
this.element = el.nativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
|
||||
if (!changes.name || !this.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldElement = this.newElement ? this.newElement : this.element;
|
||||
|
||||
// Use a new created element to avoid ion-icon working.
|
||||
// This is necessary to make the FontAwesome stuff work.
|
||||
// It is also required to stop Ionic overriding the aria-label attribute.
|
||||
this.newElement = document.createElement('ion-icon');
|
||||
if (this.name.startsWith('fa-')) {
|
||||
this.newElement.classList.add('fa');
|
||||
this.newElement.classList.add(this.name);
|
||||
if (this.isTrueProperty(this.fixedWidth)) {
|
||||
this.newElement.classList.add('fa-fw');
|
||||
}
|
||||
if (this.color) {
|
||||
this.newElement.classList.add('fa-' + this.color);
|
||||
}
|
||||
}
|
||||
|
||||
!this.ariaLabel && this.newElement.setAttribute('aria-hidden', 'true');
|
||||
!this.ariaLabel && this.newElement.setAttribute('role', 'presentation');
|
||||
this.ariaLabel && this.newElement.setAttribute('aria-label', this.ariaLabel);
|
||||
this.ariaLabel && this.newElement.setAttribute('title', this.ariaLabel);
|
||||
|
||||
const attrs = this.element.attributes;
|
||||
for (let i = attrs.length - 1; i >= 0; i--) {
|
||||
if (attrs[i].name == 'class') {
|
||||
// We don't want to override the classes we already added. Add them one by one.
|
||||
if (attrs[i].value) {
|
||||
const classes = attrs[i].value.split(' ');
|
||||
for (let j = 0; j < classes.length; j++) {
|
||||
if (classes[j]) {
|
||||
this.newElement.classList.add(classes[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
this.newElement.setAttribute(attrs[i].name, attrs[i].value);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.slash) {
|
||||
this.newElement.classList.add('icon-slash');
|
||||
}
|
||||
|
||||
if (this.flipRtl) {
|
||||
this.newElement.classList.add('core-icon-dir-flip');
|
||||
}
|
||||
|
||||
oldElement.parentElement.replaceChild(this.newElement, oldElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the value is true or on.
|
||||
*
|
||||
* @param val value to be checked.
|
||||
* @return If has a value equivalent to true.
|
||||
*/
|
||||
isTrueProperty(val: any): boolean {
|
||||
if (typeof val === 'string') {
|
||||
val = val.toLowerCase().trim();
|
||||
|
||||
return (val === 'true' || val === 'on' || val === '');
|
||||
}
|
||||
|
||||
return !!val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
if (this.newElement) {
|
||||
this.newElement.remove();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// (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 { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Pipe to search URLs that are not inside <a> tags and add the corresponding <a> tags.
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'coreCreateLinks',
|
||||
})
|
||||
export class CoreCreateLinksPipe implements PipeTransform {
|
||||
protected static replacePattern = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])(?![^<]*>|[^<>]*<\/)/gim;
|
||||
|
||||
/**
|
||||
* Takes some text and adds anchor tags to all links that aren't inside anchors.
|
||||
*
|
||||
* @param text Text to treat.
|
||||
* @return Treated text.
|
||||
*/
|
||||
transform(text: string): string {
|
||||
return text.replace(CoreCreateLinksPipe.replacePattern, '<a href="$1">$1</a>');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// (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 { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Pipe to remove HTML tags.
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'coreNoTags',
|
||||
})
|
||||
export class CoreNoTagsPipe implements PipeTransform {
|
||||
|
||||
/**
|
||||
* Takes a text and removes HTML tags.
|
||||
*
|
||||
* @param text The text to treat.
|
||||
* @return Treated text.
|
||||
*/
|
||||
transform(text: string): string {
|
||||
return text.replace(/(<([^>]+)>)/ig, '');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
import { CoreCreateLinksPipe } from './create-links.pipe';
|
||||
import { CoreNoTagsPipe } from './no-tags.pipe';
|
||||
import { CoreTimeAgoPipe } from './time-ago.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CoreCreateLinksPipe,
|
||||
CoreNoTagsPipe,
|
||||
CoreTimeAgoPipe,
|
||||
],
|
||||
imports: [],
|
||||
exports: [
|
||||
CoreCreateLinksPipe,
|
||||
CoreNoTagsPipe,
|
||||
CoreTimeAgoPipe,
|
||||
]
|
||||
})
|
||||
export class CorePipesModule {}
|
|
@ -0,0 +1,53 @@
|
|||
// (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 { Pipe, PipeTransform } from '@angular/core';
|
||||
import { Translate } from '@singletons/core.singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* Pipe to turn a UNIX timestamp to "time ago".
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'coreTimeAgo',
|
||||
})
|
||||
export class CoreTimeAgoPipe implements PipeTransform {
|
||||
private logger: CoreLogger;
|
||||
|
||||
constructor() {
|
||||
this.logger = CoreLogger.getInstance('CoreTimeAgoPipe');
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a UNIX timestamp to "time ago".
|
||||
*
|
||||
* @param timestamp The UNIX timestamp (without milliseconds).
|
||||
* @return Formatted time.
|
||||
*/
|
||||
transform(timestamp: string | number): string {
|
||||
if (typeof timestamp == 'string') {
|
||||
// Convert the value to a number.
|
||||
const numberTimestamp = parseInt(timestamp, 10);
|
||||
if (isNaN(numberTimestamp)) {
|
||||
this.logger.error('Invalid value received', timestamp);
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
timestamp = numberTimestamp;
|
||||
}
|
||||
|
||||
return Translate.instance.instant('core.ago', {$a: moment(timestamp * 1000).fromNow(true)});
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -70,7 +70,7 @@ export class CoreGeolocationProvider {
|
|||
}
|
||||
|
||||
if (!CoreApp.instance.isIOS()) {
|
||||
await Diagnostic.instance.switchToLocationSettings();
|
||||
Diagnostic.instance.switchToLocationSettings();
|
||||
await CoreApp.instance.waitForResume(30000);
|
||||
|
||||
locationEnabled = await Diagnostic.instance.isLocationEnabled();
|
||||
|
@ -91,8 +91,7 @@ export class CoreGeolocationProvider {
|
|||
const authorizationStatus = await Diagnostic.instance.getLocationAuthorizationStatus();
|
||||
|
||||
switch (authorizationStatus) {
|
||||
// This constant is hard-coded because it is not declared in @ionic-native/diagnostic v4.
|
||||
case 'DENIED_ONCE':
|
||||
case Diagnostic.instance.permissionStatus.DENIED_ONCE:
|
||||
if (failOnDeniedOnce) {
|
||||
throw new CoreGeolocationError(CoreGeolocationErrorReason.PermissionDenied);
|
||||
}
|
||||
|
@ -107,7 +106,6 @@ export class CoreGeolocationProvider {
|
|||
case Diagnostic.instance.permissionStatus.GRANTED_WHEN_IN_USE:
|
||||
// Location is authorized.
|
||||
return;
|
||||
case Diagnostic.instance.permissionStatus.DENIED:
|
||||
default:
|
||||
throw new CoreGeolocationError(CoreGeolocationErrorReason.PermissionDenied);
|
||||
}
|
||||
|
@ -133,7 +131,7 @@ export enum CoreGeolocationErrorReason {
|
|||
|
||||
export class CoreGeolocationError extends CoreError {
|
||||
|
||||
readonly reason: CoreGeolocationErrorReason;
|
||||
reason: CoreGeolocationErrorReason;
|
||||
|
||||
constructor(reason: CoreGeolocationErrorReason) {
|
||||
super(`GeolocationError: ${reason}`);
|
||||
|
|
|
@ -17,19 +17,20 @@ import { Injectable } from '@angular/core';
|
|||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
||||
import { CoreWSExternalWarning } from '@services/ws';
|
||||
import { CoreCourseBase } from '@/types/global';
|
||||
|
||||
/*
|
||||
* Service to handle groups.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreGroupsProvider {
|
||||
// Group mode constants.
|
||||
static NOGROUPS = 0;
|
||||
static SEPARATEGROUPS = 1;
|
||||
static VISIBLEGROUPS = 2;
|
||||
protected ROOT_CACHE_KEY = 'mmGroups:';
|
||||
|
||||
constructor() { }
|
||||
// Group mode constants.
|
||||
static readonly NOGROUPS = 0;
|
||||
static readonly SEPARATEGROUPS = 1;
|
||||
static readonly VISIBLEGROUPS = 2;
|
||||
protected readonly ROOT_CACHE_KEY = 'mmGroups:';
|
||||
|
||||
/**
|
||||
* Check if group mode of an activity is enabled.
|
||||
|
@ -39,12 +40,14 @@ export class CoreGroupsProvider {
|
|||
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
|
||||
* @return Promise resolved with true if the activity has groups, resolved with false otherwise.
|
||||
*/
|
||||
activityHasGroups(cmId: number, siteId?: string, ignoreCache?: boolean): Promise<boolean> {
|
||||
return this.getActivityGroupMode(cmId, siteId, ignoreCache).then((groupmode) => {
|
||||
async activityHasGroups(cmId: number, siteId?: string, ignoreCache?: boolean): Promise<boolean> {
|
||||
try {
|
||||
const groupmode = await this.getActivityGroupMode(cmId, siteId, ignoreCache);
|
||||
|
||||
return groupmode === CoreGroupsProvider.SEPARATEGROUPS || groupmode === CoreGroupsProvider.VISIBLEGROUPS;
|
||||
}).catch(() => {
|
||||
} catch (error) {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,32 +59,32 @@ export class CoreGroupsProvider {
|
|||
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
|
||||
* @return Promise resolved when the groups are retrieved.
|
||||
*/
|
||||
getActivityAllowedGroups(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise<any> {
|
||||
return CoreSites.instance.getSite(siteId).then((site) => {
|
||||
userId = userId || site.getUserId();
|
||||
async getActivityAllowedGroups(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean):
|
||||
Promise<CoreGroupGetActivityAllowedGroupsResponse> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
const params = {
|
||||
cmid: cmId,
|
||||
userid: userId,
|
||||
};
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getActivityAllowedGroupsCacheKey(cmId, userId),
|
||||
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||
};
|
||||
userId = userId || site.getUserId();
|
||||
|
||||
if (ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
const params = {
|
||||
cmid: cmId,
|
||||
userid: userId,
|
||||
};
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getActivityAllowedGroupsCacheKey(cmId, userId),
|
||||
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||
};
|
||||
|
||||
return site.read('core_group_get_activity_allowed_groups', params, preSets).then((response) => {
|
||||
if (!response || !response.groups) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
if (ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
});
|
||||
const response = await site.read('core_group_get_activity_allowed_groups', params, preSets);
|
||||
if (!response || !response.groups) {
|
||||
throw null;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,20 +107,20 @@ export class CoreGroupsProvider {
|
|||
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
|
||||
* @return Promise resolved when the groups are retrieved. If not allowed, empty array will be returned.
|
||||
*/
|
||||
getActivityAllowedGroupsIfEnabled(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise<any[]> {
|
||||
async getActivityAllowedGroupsIfEnabled(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean):
|
||||
Promise<CoreGroupGetActivityAllowedGroupsResponse> {
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
// Get real groupmode, in case it's forced by the course.
|
||||
return this.activityHasGroups(cmId, siteId, ignoreCache).then((hasGroups) => {
|
||||
if (hasGroups) {
|
||||
// Get the groups available for the user.
|
||||
return this.getActivityAllowedGroups(cmId, userId, siteId, ignoreCache);
|
||||
}
|
||||
const hasGroups = await this.activityHasGroups(cmId, siteId, ignoreCache);
|
||||
if (hasGroups) {
|
||||
// Get the groups available for the user.
|
||||
return this.getActivityAllowedGroups(cmId, userId, siteId, ignoreCache);
|
||||
}
|
||||
|
||||
return {
|
||||
groups: []
|
||||
};
|
||||
});
|
||||
return {
|
||||
groups: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,44 +133,43 @@ export class CoreGroupsProvider {
|
|||
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
|
||||
* @return Promise resolved with the group info.
|
||||
*/
|
||||
getActivityGroupInfo(cmId: number, addAllParts?: boolean, userId?: number, siteId?: string, ignoreCache?: boolean)
|
||||
: Promise<CoreGroupInfo> {
|
||||
|
||||
async getActivityGroupInfo(cmId: number, addAllParts?: boolean, userId?: number, siteId?: string, ignoreCache?: boolean):
|
||||
Promise<CoreGroupInfo> {
|
||||
const groupInfo: CoreGroupInfo = {
|
||||
groups: []
|
||||
groups: [],
|
||||
};
|
||||
|
||||
return this.getActivityGroupMode(cmId, siteId, ignoreCache).then((groupMode) => {
|
||||
groupInfo.separateGroups = groupMode === CoreGroupsProvider.SEPARATEGROUPS;
|
||||
groupInfo.visibleGroups = groupMode === CoreGroupsProvider.VISIBLEGROUPS;
|
||||
const groupMode = await this.getActivityGroupMode(cmId, siteId, ignoreCache);
|
||||
|
||||
if (groupInfo.separateGroups || groupInfo.visibleGroups) {
|
||||
return this.getActivityAllowedGroups(cmId, userId, siteId, ignoreCache);
|
||||
}
|
||||
groupInfo.separateGroups = groupMode === CoreGroupsProvider.SEPARATEGROUPS;
|
||||
groupInfo.visibleGroups = groupMode === CoreGroupsProvider.VISIBLEGROUPS;
|
||||
|
||||
return {
|
||||
let result: CoreGroupGetActivityAllowedGroupsResponse;
|
||||
if (groupInfo.separateGroups || groupInfo.visibleGroups) {
|
||||
result = await this.getActivityAllowedGroups(cmId, userId, siteId, ignoreCache);
|
||||
} else {
|
||||
result = {
|
||||
groups: [],
|
||||
canaccessallgroups: false
|
||||
};
|
||||
}).then((result) => {
|
||||
if (result.groups.length <= 0) {
|
||||
groupInfo.separateGroups = false;
|
||||
groupInfo.visibleGroups = false;
|
||||
}
|
||||
|
||||
if (result.groups.length <= 0) {
|
||||
groupInfo.separateGroups = false;
|
||||
groupInfo.visibleGroups = false;
|
||||
groupInfo.defaultGroupId = 0;
|
||||
} else {
|
||||
// The "canaccessallgroups" field was added in 3.4. Add all participants for visible groups in previous versions.
|
||||
if (result.canaccessallgroups || (typeof result.canaccessallgroups == 'undefined' && groupInfo.visibleGroups)) {
|
||||
groupInfo.groups.push({ id: 0, name: Translate.instance.instant('core.allparticipants') });
|
||||
groupInfo.defaultGroupId = 0;
|
||||
} else {
|
||||
// The "canaccessallgroups" field was added in 3.4. Add all participants for visible groups in previous versions.
|
||||
if (result.canaccessallgroups || (typeof result.canaccessallgroups == 'undefined' && groupInfo.visibleGroups)) {
|
||||
groupInfo.groups.push({ id: 0, name: Translate.instance.instant('core.allparticipants') });
|
||||
groupInfo.defaultGroupId = 0;
|
||||
} else {
|
||||
groupInfo.defaultGroupId = result.groups[0].id;
|
||||
}
|
||||
|
||||
groupInfo.groups = groupInfo.groups.concat(result.groups);
|
||||
groupInfo.defaultGroupId = result.groups[0].id;
|
||||
}
|
||||
|
||||
return groupInfo;
|
||||
});
|
||||
groupInfo.groups = groupInfo.groups.concat(result.groups);
|
||||
}
|
||||
|
||||
return groupInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -178,29 +180,27 @@ export class CoreGroupsProvider {
|
|||
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
|
||||
* @return Promise resolved when the group mode is retrieved.
|
||||
*/
|
||||
getActivityGroupMode(cmId: number, siteId?: string, ignoreCache?: boolean): Promise<number> {
|
||||
return CoreSites.instance.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
cmid: cmId,
|
||||
};
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getActivityGroupModeCacheKey(cmId),
|
||||
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||
};
|
||||
async getActivityGroupMode(cmId: number, siteId?: string, ignoreCache?: boolean): Promise<number> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
const params = {
|
||||
cmid: cmId,
|
||||
};
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getActivityGroupModeCacheKey(cmId),
|
||||
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||
};
|
||||
|
||||
if (ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
if (ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
return site.read('core_group_get_activity_groupmode', params, preSets).then((response) => {
|
||||
if (!response || typeof response.groupmode == 'undefined') {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
const response = await site.read('core_group_get_activity_groupmode', params, preSets);
|
||||
if (!response || typeof response.groupmode == 'undefined') {
|
||||
throw null;
|
||||
}
|
||||
|
||||
return response.groupmode;
|
||||
});
|
||||
});
|
||||
return response.groupmode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -219,16 +219,15 @@ export class CoreGroupsProvider {
|
|||
* @param siteId Site to get the groups from. If not defined, use current site.
|
||||
* @return Promise resolved when the groups are retrieved.
|
||||
*/
|
||||
getAllUserGroups(siteId?: string): Promise<any[]> {
|
||||
return CoreSites.instance.getSite(siteId).then((site) => {
|
||||
siteId = siteId || site.getId();
|
||||
async getAllUserGroups(siteId?: string): Promise<CoreGroup[]> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
siteId = siteId || site.getId();
|
||||
|
||||
if (site.isVersionGreaterEqualThan('3.6')) {
|
||||
return this.getUserGroupsInCourse(0, siteId);
|
||||
}
|
||||
if (site.isVersionGreaterEqualThan('3.6')) {
|
||||
return this.getUserGroupsInCourse(0, siteId);
|
||||
}
|
||||
|
||||
// @todo Get courses.
|
||||
});
|
||||
// @todo Get courses.
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -239,17 +238,13 @@ export class CoreGroupsProvider {
|
|||
* @param userId ID of the user. If not defined, use the userId related to siteId.
|
||||
* @return Promise resolved when the groups are retrieved.
|
||||
*/
|
||||
getUserGroups(courses: any[], siteId?: string, userId?: number): Promise<any[]> {
|
||||
async getUserGroups(courses: CoreCourseBase[] | number[], siteId?: string, userId?: number): Promise<CoreGroup[]> {
|
||||
// Get all courses one by one.
|
||||
const promises = courses.map((course) => {
|
||||
const courseId = typeof course == 'object' ? course.id : course;
|
||||
const promises = this.getCourseIds(courses).map((courseId) => this.getUserGroupsInCourse(courseId, siteId, userId));
|
||||
|
||||
return this.getUserGroupsInCourse(courseId, siteId, userId);
|
||||
});
|
||||
const courseGroups = await Promise.all(promises);
|
||||
|
||||
return Promise.all(promises).then((courseGroups) => {
|
||||
return [].concat(...courseGroups);
|
||||
});
|
||||
return [].concat(...courseGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -260,26 +255,24 @@ export class CoreGroupsProvider {
|
|||
* @param userId ID of the user. If not defined, use ID related to siteid.
|
||||
* @return Promise resolved when the groups are retrieved.
|
||||
*/
|
||||
getUserGroupsInCourse(courseId: number, siteId?: string, userId?: number): Promise<any[]> {
|
||||
return CoreSites.instance.getSite(siteId).then((site) => {
|
||||
userId = userId || site.getUserId();
|
||||
const data = {
|
||||
userid: userId,
|
||||
courseid: courseId,
|
||||
};
|
||||
const preSets = {
|
||||
cacheKey: this.getUserGroupsInCourseCacheKey(courseId, userId),
|
||||
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||
};
|
||||
async getUserGroupsInCourse(courseId: number, siteId?: string, userId?: number): Promise<CoreGroup[]> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
userId = userId || site.getUserId();
|
||||
const data = {
|
||||
userid: userId,
|
||||
courseid: courseId,
|
||||
};
|
||||
const preSets = {
|
||||
cacheKey: this.getUserGroupsInCourseCacheKey(courseId, userId),
|
||||
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||
};
|
||||
|
||||
return site.read('core_group_get_course_user_groups', data, preSets).then((response) => {
|
||||
if (response && response.groups) {
|
||||
return response.groups;
|
||||
} else {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
const response = await site.read('core_group_get_course_user_groups', data, preSets);
|
||||
if (!response || !response.groups) {
|
||||
throw null;
|
||||
}
|
||||
|
||||
return response.groups;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -310,12 +303,11 @@ export class CoreGroupsProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateActivityAllowedGroups(cmId: number, userId?: number, siteId?: string): Promise<any> {
|
||||
return CoreSites.instance.getSite(siteId).then((site) => {
|
||||
userId = userId || site.getUserId();
|
||||
async invalidateActivityAllowedGroups(cmId: number, userId?: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
userId = userId || site.getUserId();
|
||||
|
||||
return site.invalidateWsCacheForKey(this.getActivityAllowedGroupsCacheKey(cmId, userId));
|
||||
});
|
||||
await site.invalidateWsCacheForKey(this.getActivityAllowedGroupsCacheKey(cmId, userId));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -325,10 +317,10 @@ export class CoreGroupsProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateActivityGroupMode(cmId: number, siteId?: string): Promise<any> {
|
||||
return CoreSites.instance.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getActivityGroupModeCacheKey(cmId));
|
||||
});
|
||||
async invalidateActivityGroupMode(cmId: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
await site.invalidateWsCacheForKey(this.getActivityGroupModeCacheKey(cmId));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -339,12 +331,12 @@ export class CoreGroupsProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateActivityGroupInfo(cmId: number, userId?: number, siteId?: string): Promise<any> {
|
||||
async invalidateActivityGroupInfo(cmId: number, userId?: number, siteId?: string): Promise<void> {
|
||||
const promises = [];
|
||||
promises.push(this.invalidateActivityAllowedGroups(cmId, userId, siteId));
|
||||
promises.push(this.invalidateActivityGroupMode(cmId, siteId));
|
||||
|
||||
return Promise.all(promises);
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -353,14 +345,14 @@ export class CoreGroupsProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateAllUserGroups(siteId?: string): Promise<any> {
|
||||
return CoreSites.instance.getSite(siteId).then((site) => {
|
||||
if (site.isVersionGreaterEqualThan('3.6')) {
|
||||
return this.invalidateUserGroupsInCourse(0, siteId);
|
||||
}
|
||||
async invalidateAllUserGroups(siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
return site.invalidateWsCacheForKeyStartingWith(this.getUserGroupsInCoursePrefixCacheKey());
|
||||
});
|
||||
if (site.isVersionGreaterEqualThan('3.6')) {
|
||||
return this.invalidateUserGroupsInCourse(0, siteId);
|
||||
}
|
||||
|
||||
await site.invalidateWsCacheForKeyStartingWith(this.getUserGroupsInCoursePrefixCacheKey());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -371,18 +363,13 @@ export class CoreGroupsProvider {
|
|||
* @param userId User ID. If not defined, use current user.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateUserGroups(courses: any[], siteId?: string, userId?: number): Promise<any> {
|
||||
return CoreSites.instance.getSite(siteId).then((site) => {
|
||||
userId = userId || site.getUserId();
|
||||
async invalidateUserGroups(courses: CoreCourseBase[] | number[], siteId?: string, userId?: number): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
userId = userId || site.getUserId();
|
||||
|
||||
const promises = courses.map((course) => {
|
||||
const courseId = typeof course == 'object' ? course.id : course;
|
||||
const promises = this.getCourseIds(courses).map((courseId) => this.invalidateUserGroupsInCourse(courseId, site.id, userId));
|
||||
|
||||
return this.invalidateUserGroupsInCourse(courseId, site.id, userId);
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -393,12 +380,11 @@ export class CoreGroupsProvider {
|
|||
* @param userId User ID. If not defined, use current user.
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateUserGroupsInCourse(courseId: number, siteId?: string, userId?: number): Promise<any> {
|
||||
return CoreSites.instance.getSite(siteId).then((site) => {
|
||||
userId = userId || site.getUserId();
|
||||
async invalidateUserGroupsInCourse(courseId: number, siteId?: string, userId?: number): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
userId = userId || site.getUserId();
|
||||
|
||||
return site.invalidateWsCacheForKey(this.getUserGroupsInCourseCacheKey(courseId, userId));
|
||||
});
|
||||
await site.invalidateWsCacheForKey(this.getUserGroupsInCourseCacheKey(courseId, userId));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -418,10 +404,30 @@ export class CoreGroupsProvider {
|
|||
|
||||
return groupInfo.defaultGroupId;
|
||||
}
|
||||
|
||||
protected getCourseIds(courses: CoreCourseBase[] | number[]): number[] {
|
||||
return courses.length > 0 && typeof courses[0] === 'object'
|
||||
? (courses as CoreCourseBase[]).map((course) => course.id)
|
||||
: courses as number[];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreGroups extends makeSingleton(CoreGroupsProvider) {}
|
||||
|
||||
/**
|
||||
* Specific group info.
|
||||
*/
|
||||
export type CoreGroup = {
|
||||
id: number; // Group ID.
|
||||
name: string; // Multilang compatible name, course unique'.
|
||||
description?: string; // Group description text.
|
||||
descriptionformat?: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
|
||||
idnumber?: string; // Id number.
|
||||
courseid?: number; // Coure Id.
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Group info for an activity.
|
||||
*/
|
||||
|
@ -429,7 +435,7 @@ export type CoreGroupInfo = {
|
|||
/**
|
||||
* List of groups.
|
||||
*/
|
||||
groups?: any[];
|
||||
groups?: CoreGroup[];
|
||||
|
||||
/**
|
||||
* Whether it's separate groups.
|
||||
|
@ -446,3 +452,12 @@ export type CoreGroupInfo = {
|
|||
*/
|
||||
defaultGroupId?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* WS core_group_get_activity_allowed_groups response type.
|
||||
*/
|
||||
export type CoreGroupGetActivityAllowedGroupsResponse = {
|
||||
groups: CoreGroup[]; // List of groups.
|
||||
canaccessallgroups?: boolean; // Whether the user will be able to access all the activity groups.
|
||||
warnings?: CoreWSExternalWarning[];
|
||||
};
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
// (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 { CoreCronHandler } from '@services/cron';
|
||||
import { CoreSites } from '@services/sites';
|
||||
|
||||
/**
|
||||
* Cron handler to update site info every certain time.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreSiteInfoCronHandler implements CoreCronHandler {
|
||||
|
||||
name = 'CoreSiteInfoCronHandler';
|
||||
|
||||
/**
|
||||
* Execute the process.
|
||||
* Receives the ID of the site affected, undefined for all sites.
|
||||
*
|
||||
* @param siteId ID of the site affected, undefined for all sites.
|
||||
* @return Promise resolved when done, rejected on failure.
|
||||
*/
|
||||
async execute(siteId?: string): Promise<void> {
|
||||
if (!siteId) {
|
||||
const siteIds = await CoreSites.instance.getSitesIds();
|
||||
|
||||
await Promise.all(siteIds.map((siteId) => CoreSites.instance.updateSiteInfo(siteId)));
|
||||
} else {
|
||||
await CoreSites.instance.updateSiteInfo(siteId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns handler's interval in milliseconds. Defaults to CoreCronDelegate.DEFAULT_INTERVAL.
|
||||
*
|
||||
* @return Interval time (in milliseconds).
|
||||
*/
|
||||
getInterval(): number {
|
||||
return 10800000; // 3 hours.
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether it's a synchronization process or not. True if not defined.
|
||||
*
|
||||
* @return Whether it's a synchronization process or not.
|
||||
*/
|
||||
isSync(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreUtils, PromiseDefer } from '@services/utils/utils';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
|
||||
|
@ -50,12 +50,13 @@ export type CoreInitHandler = {
|
|||
*/
|
||||
@Injectable()
|
||||
export class CoreInitDelegate {
|
||||
|
||||
static readonly DEFAULT_PRIORITY = 100; // Default priority for init processes.
|
||||
static readonly MAX_RECOMMENDED_PRIORITY = 600;
|
||||
|
||||
protected initProcesses = {};
|
||||
protected initProcesses: { [s: string]: CoreInitHandler } = {};
|
||||
protected logger: CoreLogger;
|
||||
protected readiness;
|
||||
protected readiness: CoreInitReadinessPromiseDefer<void>;
|
||||
|
||||
constructor() {
|
||||
this.logger = CoreLogger.getInstance('CoreInitDelegate');
|
||||
|
@ -163,6 +164,14 @@ export class CoreInitDelegate {
|
|||
this.logger.log(`Registering process '${handler.name}'.`);
|
||||
this.initProcesses[handler.name] = handler;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreInit extends makeSingleton(CoreInitDelegate) {}
|
||||
|
||||
/**
|
||||
* Deferred promise for init readiness.
|
||||
*/
|
||||
type CoreInitReadinessPromiseDefer<T> = PromiseDefer<T> & {
|
||||
resolved?: boolean; // If true, readiness have been resolved.
|
||||
};
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
import CoreConfigConstants from '@app/config.json';
|
||||
import { CoreApp, CoreAppProvider } from '@services/app';
|
||||
import { CoreAppProvider } from '@services/app';
|
||||
import { CoreConfig } from '@services/config';
|
||||
import { makeSingleton, Translate, Platform, Globalization } from '@singletons/core.singletons';
|
||||
|
||||
|
@ -26,6 +26,7 @@ import * as moment from 'moment';
|
|||
*/
|
||||
@Injectable()
|
||||
export class CoreLangProvider {
|
||||
|
||||
protected fallbackLanguage = 'en'; // Always use English as fallback language since it contains all strings.
|
||||
protected defaultLanguage = CoreConfigConstants.default_lang || 'en'; // Lang to use if device lang not valid or is forced.
|
||||
protected currentLanguage: string; // Save current language in a variable to speed up the get function.
|
||||
|
@ -51,7 +52,7 @@ export class CoreLangProvider {
|
|||
});
|
||||
});
|
||||
|
||||
Translate.instance.onLangChange.subscribe((event: any) => {
|
||||
Translate.instance.onLangChange.subscribe(() => {
|
||||
// @todo: Set platform lang and dir.
|
||||
});
|
||||
}
|
||||
|
@ -63,7 +64,7 @@ export class CoreLangProvider {
|
|||
* @param strings Object with the strings to add.
|
||||
* @param prefix A prefix to add to all keys.
|
||||
*/
|
||||
addSitePluginsStrings(lang: string, strings: any, prefix?: string): void {
|
||||
addSitePluginsStrings(lang: string, strings: string[], prefix?: string): void {
|
||||
lang = lang.replace(/_/g, '-'); // Use the app format instead of Moodle format.
|
||||
|
||||
// Initialize structure if it doesn't exist.
|
||||
|
@ -109,7 +110,7 @@ export class CoreLangProvider {
|
|||
* @param language New language to use.
|
||||
* @return Promise resolved when the change is finished.
|
||||
*/
|
||||
changeCurrentLanguage(language: string): Promise<any> {
|
||||
changeCurrentLanguage(language: string): Promise<unknown> {
|
||||
const promises = [];
|
||||
|
||||
// Change the language, resolving the promise when we receive the first value.
|
||||
|
@ -127,7 +128,7 @@ export class CoreLangProvider {
|
|||
setTimeout(() => {
|
||||
fallbackSubs.unsubscribe();
|
||||
});
|
||||
}, (error) => {
|
||||
}, () => {
|
||||
// Resolve with the original language.
|
||||
resolve(data);
|
||||
|
||||
|
@ -168,7 +169,7 @@ export class CoreLangProvider {
|
|||
// Load the custom and site plugins strings for the language.
|
||||
if (this.loadLangStrings(this.customStrings, language) || this.loadLangStrings(this.sitePluginsStrings, language)) {
|
||||
// Some lang strings have changed, emit an event to update the pipes.
|
||||
Translate.instance.onLangChange.emit({lang: language, translations: Translate.instance.translations[language]});
|
||||
Translate.instance.onLangChange.emit({ lang: language, translations: Translate.instance.translations[language] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -195,7 +196,7 @@ export class CoreLangProvider {
|
|||
*
|
||||
* @return Custom strings.
|
||||
*/
|
||||
getAllCustomStrings(): any {
|
||||
getAllCustomStrings(): unknown {
|
||||
return this.customStrings;
|
||||
}
|
||||
|
||||
|
@ -204,7 +205,7 @@ export class CoreLangProvider {
|
|||
*
|
||||
* @return Site plugins strings.
|
||||
*/
|
||||
getAllSitePluginsStrings(): any {
|
||||
getAllSitePluginsStrings(): unknown {
|
||||
return this.sitePluginsStrings;
|
||||
}
|
||||
|
||||
|
@ -213,16 +214,13 @@ export class CoreLangProvider {
|
|||
*
|
||||
* @return Promise resolved with the current language.
|
||||
*/
|
||||
getCurrentLanguage(): Promise<string> {
|
||||
|
||||
async getCurrentLanguage(): Promise<string> {
|
||||
if (typeof this.currentLanguage != 'undefined') {
|
||||
return Promise.resolve(this.currentLanguage);
|
||||
return this.currentLanguage;
|
||||
}
|
||||
|
||||
// Get current language from config (user might have changed it).
|
||||
return CoreConfig.instance.get('current_language').then((language) => {
|
||||
return language;
|
||||
}).catch(() => {
|
||||
return CoreConfig.instance.get('current_language').then((language) => language).catch(() => {
|
||||
// User hasn't defined a language. If default language is forced, use it.
|
||||
if (CoreConfigConstants.default_lang && CoreConfigConstants.forcedefaultlanguage) {
|
||||
return CoreConfigConstants.default_lang;
|
||||
|
@ -237,7 +235,6 @@ export class CoreLangProvider {
|
|||
if (CoreConfigConstants.languages && typeof CoreConfigConstants.languages[language] == 'undefined') {
|
||||
// Code is NOT supported. Fallback to language without dash. E.g. 'en-US' would fallback to 'en'.
|
||||
language = language.substr(0, language.indexOf('-'));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,10 +244,10 @@ export class CoreLangProvider {
|
|||
}
|
||||
|
||||
return language;
|
||||
}).catch(() => {
|
||||
}).catch(() =>
|
||||
// Error getting locale. Use default language.
|
||||
return this.defaultLanguage;
|
||||
});
|
||||
this.defaultLanguage,
|
||||
);
|
||||
} catch (err) {
|
||||
// Error getting locale. Use default language.
|
||||
return Promise.resolve(this.defaultLanguage);
|
||||
|
@ -286,7 +283,7 @@ export class CoreLangProvider {
|
|||
* @param lang The language to check.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
getTranslationTable(lang: string): Promise<any> {
|
||||
getTranslationTable(lang: string): Promise<unknown> {
|
||||
// Create a promise to convert the observable into a promise.
|
||||
return new Promise((resolve, reject): void => {
|
||||
const observer = Translate.instance.getTranslation(lang).subscribe((table) => {
|
||||
|
@ -322,14 +319,13 @@ export class CoreLangProvider {
|
|||
const list: string[] = strings.split(/(?:\r\n|\r|\n)/);
|
||||
list.forEach((entry: string) => {
|
||||
const values: string[] = entry.split('|');
|
||||
let lang: string;
|
||||
|
||||
if (values.length < 3) {
|
||||
// Not enough data, ignore the entry.
|
||||
return;
|
||||
}
|
||||
|
||||
lang = values[2].replace(/_/g, '-'); // Use the app format instead of Moodle format.
|
||||
const lang = values[2].replace(/_/g, '-'); // Use the app format instead of Moodle format.
|
||||
|
||||
if (lang == this.currentLanguage) {
|
||||
currentLangChanged = true;
|
||||
|
@ -353,7 +349,7 @@ export class CoreLangProvider {
|
|||
// Some lang strings have changed, emit an event to update the pipes.
|
||||
Translate.instance.onLangChange.emit({
|
||||
lang: this.currentLanguage,
|
||||
translations: Translate.instance.translations[this.currentLanguage]
|
||||
translations: Translate.instance.translations[this.currentLanguage],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -365,7 +361,7 @@ export class CoreLangProvider {
|
|||
* @param lang Language to load.
|
||||
* @return Whether the translation table was modified.
|
||||
*/
|
||||
loadLangStrings(langObject: any, lang: string): boolean {
|
||||
loadLangStrings(langObject: CoreLanguageObject, lang: string): boolean {
|
||||
let langApplied = false;
|
||||
|
||||
if (langObject[lang]) {
|
||||
|
@ -396,7 +392,7 @@ export class CoreLangProvider {
|
|||
* @param key String key.
|
||||
* @param value String value.
|
||||
*/
|
||||
loadString(langObject: any, lang: string, key: string, value: string): void {
|
||||
loadString(langObject: CoreLanguageObject, lang: string, key: string, value: string): void {
|
||||
lang = lang.replace(/_/g, '-'); // Use the app format instead of Moodle format.
|
||||
|
||||
if (Translate.instance.translations[lang]) {
|
||||
|
@ -425,7 +421,7 @@ export class CoreLangProvider {
|
|||
*
|
||||
* @param strings Strings to unload.
|
||||
*/
|
||||
protected unloadStrings(strings: any): void {
|
||||
protected unloadStrings(strings: CoreLanguageObject): void {
|
||||
// Iterate over all languages and strings.
|
||||
for (const lang in strings) {
|
||||
if (!Translate.instance.translations[lang]) {
|
||||
|
@ -446,6 +442,20 @@ export class CoreLangProvider {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreLang extends makeSingleton(CoreLangProvider) {}
|
||||
|
||||
/**
|
||||
* Language object has two leves, first per language and second per string key.
|
||||
*/
|
||||
type CoreLanguageObject = {
|
||||
[s: string]: { // Lang name.
|
||||
[s: string]: { // String key.
|
||||
value: string; // Value with replacings done.
|
||||
original?: string; // Original value of the string.
|
||||
applied?: boolean; // If the key is applied to the translations table or not.
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@ import { ILocalNotification } from '@ionic-native/local-notifications';
|
|||
|
||||
import { CoreApp, CoreAppSchema } from '@services/app';
|
||||
import { CoreConfig } from '@services/config';
|
||||
import { CoreEvents, CoreEventsProvider } from '@services/events';
|
||||
import { CoreEventObserver, CoreEvents, CoreEventsProvider } from '@services/events';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { SQLiteDB } from '@classes/sqlitedb';
|
||||
|
@ -33,56 +33,57 @@ import { CoreLogger } from '@singletons/logger';
|
|||
*/
|
||||
@Injectable()
|
||||
export class CoreLocalNotificationsProvider {
|
||||
|
||||
// Variables for the database.
|
||||
protected SITES_TABLE = 'notification_sites'; // Store to asigne unique codes to each site.
|
||||
protected COMPONENTS_TABLE = 'notification_components'; // Store to asigne unique codes to each component.
|
||||
protected TRIGGERED_TABLE = 'notifications_triggered'; // Store to prevent re-triggering notifications.
|
||||
protected static readonly SITES_TABLE = 'notification_sites'; // Store to asigne unique codes to each site.
|
||||
protected static readonly COMPONENTS_TABLE = 'notification_components'; // Store to asigne unique codes to each component.
|
||||
protected static readonly TRIGGERED_TABLE = 'notifications_triggered'; // Store to prevent re-triggering notifications.
|
||||
protected tablesSchema: CoreAppSchema = {
|
||||
name: 'CoreLocalNotificationsProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: this.SITES_TABLE,
|
||||
name: CoreLocalNotificationsProvider.SITES_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'TEXT',
|
||||
primaryKey: true
|
||||
primaryKey: true,
|
||||
},
|
||||
{
|
||||
name: 'code',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
notNull: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: this.COMPONENTS_TABLE,
|
||||
name: CoreLocalNotificationsProvider.COMPONENTS_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'TEXT',
|
||||
primaryKey: true
|
||||
primaryKey: true,
|
||||
},
|
||||
{
|
||||
name: 'code',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
notNull: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: this.TRIGGERED_TABLE,
|
||||
name: CoreLocalNotificationsProvider.TRIGGERED_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'INTEGER',
|
||||
primaryKey: true
|
||||
primaryKey: true,
|
||||
},
|
||||
{
|
||||
name: 'at',
|
||||
type: 'INTEGER',
|
||||
notNull: true
|
||||
notNull: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -91,7 +92,7 @@ export class CoreLocalNotificationsProvider {
|
|||
|
||||
protected logger: CoreLogger;
|
||||
protected appDB: SQLiteDB;
|
||||
protected dbReady: Promise<any>; // Promise resolved when the app DB is initialized.
|
||||
protected dbReady: Promise<void>; // Promise resolved when the app DB is initialized.
|
||||
protected codes: { [s: string]: number } = {};
|
||||
protected codeRequestsQueue = {};
|
||||
protected observables = {};
|
||||
|
@ -99,8 +100,9 @@ export class CoreLocalNotificationsProvider {
|
|||
title: '',
|
||||
texts: [],
|
||||
ids: [],
|
||||
timeouts: []
|
||||
timeouts: [],
|
||||
};
|
||||
|
||||
protected triggerSubscription: Subscription;
|
||||
protected clickSubscription: Subscription;
|
||||
protected clearSubscription: Subscription;
|
||||
|
@ -110,7 +112,6 @@ export class CoreLocalNotificationsProvider {
|
|||
protected queueRunner: CoreQueueRunner; // Queue to decrease the number of concurrent calls to the plugin (see MOBILE-3477).
|
||||
|
||||
constructor() {
|
||||
|
||||
this.logger = CoreLogger.getInstance('CoreLocalNotificationsProvider');
|
||||
this.queueRunner = new CoreQueueRunner(10);
|
||||
this.appDB = CoreApp.instance.getDB();
|
||||
|
@ -149,7 +150,7 @@ export class CoreLocalNotificationsProvider {
|
|||
// Create the default channel for local notifications.
|
||||
this.createDefaultChannel();
|
||||
|
||||
Translate.instance.onLangChange.subscribe((event: any) => {
|
||||
Translate.instance.onLangChange.subscribe(() => {
|
||||
// Update the channel name.
|
||||
this.createDefaultChannel();
|
||||
});
|
||||
|
@ -187,7 +188,6 @@ export class CoreLocalNotificationsProvider {
|
|||
* @return Promise resolved when the notifications are cancelled.
|
||||
*/
|
||||
async cancelSiteNotifications(siteId: string): Promise<void> {
|
||||
|
||||
if (!this.isAvailable()) {
|
||||
return;
|
||||
} else if (!siteId) {
|
||||
|
@ -228,15 +228,15 @@ export class CoreLocalNotificationsProvider {
|
|||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected createDefaultChannel(): Promise<any> {
|
||||
protected async createDefaultChannel(): Promise<void> {
|
||||
if (!CoreApp.instance.isAndroid()) {
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
return Push.instance.createChannel({
|
||||
await Push.instance.createChannel({
|
||||
id: 'default-channel-id',
|
||||
description: Translate.instance.instant('addon.calendar.calendarreminders'),
|
||||
importance: 4
|
||||
importance: 4,
|
||||
}).catch((error) => {
|
||||
this.logger.error('Error changing channel name', error);
|
||||
});
|
||||
|
@ -297,7 +297,7 @@ export class CoreLocalNotificationsProvider {
|
|||
* @return Promise resolved when the component code is retrieved.
|
||||
*/
|
||||
protected getComponentCode(component: string): Promise<number> {
|
||||
return this.requestCode(this.COMPONENTS_TABLE, component);
|
||||
return this.requestCode(CoreLocalNotificationsProvider.COMPONENTS_TABLE, component);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,16 +308,16 @@ export class CoreLocalNotificationsProvider {
|
|||
* @return Promise resolved when the site code is retrieved.
|
||||
*/
|
||||
protected getSiteCode(siteId: string): Promise<number> {
|
||||
return this.requestCode(this.SITES_TABLE, siteId);
|
||||
return this.requestCode(CoreLocalNotificationsProvider.SITES_TABLE, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a unique notification ID, trying to prevent collisions. Generated ID must be a Number (Android).
|
||||
* The generated ID shouldn't be higher than 2147483647 or it's going to cause problems in Android.
|
||||
* This function will prevent collisions and keep the number under Android limit if:
|
||||
* -User has used less than 21 sites.
|
||||
* -There are less than 11 components.
|
||||
* -The notificationId passed as parameter is lower than 10000000.
|
||||
* - User has used less than 21 sites.
|
||||
* - There are less than 11 components.
|
||||
* - The notificationId passed as parameter is lower than 10000000.
|
||||
*
|
||||
* @param notificationId Notification ID.
|
||||
* @param component Component triggering the notification.
|
||||
|
@ -329,12 +329,10 @@ export class CoreLocalNotificationsProvider {
|
|||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
return this.getSiteCode(siteId).then((siteCode) => {
|
||||
return this.getComponentCode(component).then((componentCode) => {
|
||||
return this.getSiteCode(siteId).then((siteCode) => this.getComponentCode(component).then((componentCode) =>
|
||||
// We use the % operation to keep the number under Android's limit.
|
||||
return (siteCode * 100000000 + componentCode * 10000000 + notificationId) % 2147483647;
|
||||
});
|
||||
});
|
||||
(siteCode * 100000000 + componentCode * 10000000 + notificationId) % 2147483647,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -343,7 +341,7 @@ export class CoreLocalNotificationsProvider {
|
|||
* @param eventName Name of the event.
|
||||
* @param notification Notification.
|
||||
*/
|
||||
protected handleEvent(eventName: string, notification: any): void {
|
||||
protected handleEvent(eventName: string, notification: ILocalNotification): void {
|
||||
if (notification && notification.data) {
|
||||
this.logger.debug('Notification event: ' + eventName + '. Data:', notification.data);
|
||||
|
||||
|
@ -374,7 +372,7 @@ export class CoreLocalNotificationsProvider {
|
|||
await this.dbReady;
|
||||
|
||||
try {
|
||||
const stored = await this.appDB.getRecord(this.TRIGGERED_TABLE, { id: notification.id });
|
||||
const stored = await this.appDB.getRecord(CoreLocalNotificationsProvider.TRIGGERED_TABLE, { id: notification.id });
|
||||
let triggered = (notification.trigger && notification.trigger.at) || 0;
|
||||
|
||||
if (typeof triggered != 'number') {
|
||||
|
@ -443,15 +441,14 @@ export class CoreLocalNotificationsProvider {
|
|||
*/
|
||||
protected processNextRequest(): void {
|
||||
const nextKey = Object.keys(this.codeRequestsQueue)[0];
|
||||
let request,
|
||||
promise;
|
||||
let promise: Promise<void>;
|
||||
|
||||
if (typeof nextKey == 'undefined') {
|
||||
// No more requests in queue, stop.
|
||||
return;
|
||||
}
|
||||
|
||||
request = this.codeRequestsQueue[nextKey];
|
||||
const request = this.codeRequestsQueue[nextKey];
|
||||
|
||||
// Check if request is valid.
|
||||
if (typeof request == 'object' && typeof request.table != 'undefined' && typeof request.id != 'undefined') {
|
||||
|
@ -483,7 +480,7 @@ export class CoreLocalNotificationsProvider {
|
|||
* @param callback Function to call with the data received by the notification.
|
||||
* @return Object with an "off" property to stop listening for clicks.
|
||||
*/
|
||||
registerClick(component: string, callback: Function): any {
|
||||
registerClick(component: string, callback: CoreLocalNotificationsClickCallback): CoreEventObserver {
|
||||
return this.registerObserver('click', component, callback);
|
||||
}
|
||||
|
||||
|
@ -495,7 +492,7 @@ export class CoreLocalNotificationsProvider {
|
|||
* @param callback Function to call with the data received by the notification.
|
||||
* @return Object with an "off" property to stop listening for events.
|
||||
*/
|
||||
registerObserver(eventName: string, component: string, callback: Function): any {
|
||||
registerObserver<T>(eventName: string, component: string, callback: CoreLocalNotificationsClickCallback): CoreEventObserver {
|
||||
this.logger.debug(`Register observer '${component}' for event '${eventName}'.`);
|
||||
|
||||
if (typeof this.observables[eventName] == 'undefined') {
|
||||
|
@ -504,7 +501,7 @@ export class CoreLocalNotificationsProvider {
|
|||
|
||||
if (typeof this.observables[eventName][component] == 'undefined') {
|
||||
// No observable for this component, create a new one.
|
||||
this.observables[eventName][component] = new Subject<any>();
|
||||
this.observables[eventName][component] = new Subject<T>();
|
||||
}
|
||||
|
||||
this.observables[eventName][component].subscribe(callback);
|
||||
|
@ -512,7 +509,7 @@ export class CoreLocalNotificationsProvider {
|
|||
return {
|
||||
off: (): void => {
|
||||
this.observables[eventName][component].unsubscribe(callback);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -522,10 +519,10 @@ export class CoreLocalNotificationsProvider {
|
|||
* @param id Notification ID.
|
||||
* @return Promise resolved when it is removed.
|
||||
*/
|
||||
async removeTriggered(id: number): Promise<any> {
|
||||
async removeTriggered(id: number): Promise<void> {
|
||||
await this.dbReady;
|
||||
|
||||
return this.appDB.deleteRecords(this.TRIGGERED_TABLE, { id: id });
|
||||
await this.appDB.deleteRecords(CoreLocalNotificationsProvider.TRIGGERED_TABLE, { id: id });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -536,9 +533,9 @@ export class CoreLocalNotificationsProvider {
|
|||
* @return Promise resolved when the code is retrieved.
|
||||
*/
|
||||
protected requestCode(table: string, id: string): Promise<number> {
|
||||
const deferred = CoreUtils.instance.promiseDefer<number>(),
|
||||
key = table + '#' + id,
|
||||
isQueueEmpty = Object.keys(this.codeRequestsQueue).length == 0;
|
||||
const deferred = CoreUtils.instance.promiseDefer<number>();
|
||||
const key = table + '#' + id;
|
||||
const isQueueEmpty = Object.keys(this.codeRequestsQueue).length == 0;
|
||||
|
||||
if (typeof this.codeRequestsQueue[key] != 'undefined') {
|
||||
// There's already a pending request for this store and ID, add the promise to it.
|
||||
|
@ -548,7 +545,7 @@ export class CoreLocalNotificationsProvider {
|
|||
this.codeRequestsQueue[key] = {
|
||||
table: table,
|
||||
id: id,
|
||||
promises: [deferred]
|
||||
promises: [deferred],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -591,7 +588,6 @@ export class CoreLocalNotificationsProvider {
|
|||
* @return Promise resolved when the notification is scheduled.
|
||||
*/
|
||||
async schedule(notification: ILocalNotification, component: string, siteId: string, alreadyUnique?: boolean): Promise<void> {
|
||||
|
||||
if (!alreadyUnique) {
|
||||
notification.id = await this.getUniqueNotificationId(notification.id, component, siteId);
|
||||
}
|
||||
|
@ -605,12 +601,29 @@ export class CoreLocalNotificationsProvider {
|
|||
notification.smallIcon = notification.smallIcon || 'res://smallicon';
|
||||
notification.color = notification.color || CoreConfigConstants.notificoncolor;
|
||||
|
||||
const led: any = notification.led || {};
|
||||
notification.led = {
|
||||
color: led.color || 'FF9900',
|
||||
on: led.on || 1000,
|
||||
off: led.off || 1000
|
||||
};
|
||||
if (notification.led !== false) {
|
||||
let ledColor = 'FF9900';
|
||||
let ledOn = 1000;
|
||||
let ledOff = 1000;
|
||||
|
||||
if (typeof notification.led === 'string') {
|
||||
ledColor = notification.led;
|
||||
} else if (Array.isArray(notification.led)) {
|
||||
ledColor = notification.led[0] || ledColor;
|
||||
ledOn = notification.led[1] || ledOn;
|
||||
ledOff = notification.led[2] || ledOff;
|
||||
} else if (typeof notification.led === 'object') {
|
||||
ledColor = notification.led.color || ledColor;
|
||||
ledOn = notification.led.on || ledOn;
|
||||
ledOff = notification.led.off || ledOff;
|
||||
}
|
||||
|
||||
notification.led = {
|
||||
color: ledColor,
|
||||
on: ledOn,
|
||||
off: ledOff,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const queueId = 'schedule-' + notification.id;
|
||||
|
@ -626,36 +639,34 @@ export class CoreLocalNotificationsProvider {
|
|||
* @param notification Notification to schedule.
|
||||
* @return Promise resolved when scheduled.
|
||||
*/
|
||||
protected scheduleNotification(notification: ILocalNotification): Promise<void> {
|
||||
protected async scheduleNotification(notification: ILocalNotification): Promise<void> {
|
||||
// Check if the notification has been triggered already.
|
||||
return this.isTriggered(notification, false).then((triggered) => {
|
||||
// Cancel the current notification in case it gets scheduled twice.
|
||||
return LocalNotifications.instance.cancel(notification.id).finally(() => {
|
||||
if (!triggered) {
|
||||
// Check if sound is enabled for notifications.
|
||||
let promise;
|
||||
const triggered = await this.isTriggered(notification, false);
|
||||
|
||||
if (this.canDisableSound()) {
|
||||
promise = CoreConfig.instance.get(CoreConstants.SETTINGS_NOTIFICATION_SOUND, true);
|
||||
} else {
|
||||
promise = Promise.resolve(true);
|
||||
}
|
||||
// Cancel the current notification in case it gets scheduled twice.
|
||||
LocalNotifications.instance.cancel(notification.id).finally(async () => {
|
||||
if (!triggered) {
|
||||
let soundEnabled: boolean;
|
||||
|
||||
return promise.then((soundEnabled) => {
|
||||
if (!soundEnabled) {
|
||||
notification.sound = null;
|
||||
} else {
|
||||
delete notification.sound; // Use default value.
|
||||
}
|
||||
|
||||
notification.foreground = true;
|
||||
|
||||
// Remove from triggered, since the notification could be in there with a different time.
|
||||
this.removeTriggered(notification.id);
|
||||
LocalNotifications.instance.schedule(notification);
|
||||
});
|
||||
// Check if sound is enabled for notifications.
|
||||
if (!this.canDisableSound()) {
|
||||
soundEnabled = true;
|
||||
} else {
|
||||
soundEnabled = await CoreConfig.instance.get(CoreConstants.SETTINGS_NOTIFICATION_SOUND, true);
|
||||
}
|
||||
});
|
||||
|
||||
if (!soundEnabled) {
|
||||
notification.sound = null;
|
||||
} else {
|
||||
delete notification.sound; // Use default value.
|
||||
}
|
||||
|
||||
notification.foreground = true;
|
||||
|
||||
// Remove from triggered, since the notification could be in there with a different time.
|
||||
this.removeTriggered(notification.id);
|
||||
LocalNotifications.instance.schedule(notification);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -666,15 +677,15 @@ export class CoreLocalNotificationsProvider {
|
|||
* @param notification Triggered notification.
|
||||
* @return Promise resolved when stored, rejected otherwise.
|
||||
*/
|
||||
async trigger(notification: ILocalNotification): Promise<any> {
|
||||
async trigger(notification: ILocalNotification): Promise<number> {
|
||||
await this.dbReady;
|
||||
|
||||
const entry = {
|
||||
id: notification.id,
|
||||
at: notification.trigger && notification.trigger.at ? notification.trigger.at : Date.now()
|
||||
at: notification.trigger && notification.trigger.at ? notification.trigger.at : Date.now(),
|
||||
};
|
||||
|
||||
return this.appDB.insertRecord(this.TRIGGERED_TABLE, entry);
|
||||
return this.appDB.insertRecord(CoreLocalNotificationsProvider.TRIGGERED_TABLE, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -684,14 +695,17 @@ export class CoreLocalNotificationsProvider {
|
|||
* @param newName The new name.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async updateComponentName(oldName: string, newName: string): Promise<any> {
|
||||
async updateComponentName(oldName: string, newName: string): Promise<void> {
|
||||
await this.dbReady;
|
||||
|
||||
const oldId = this.COMPONENTS_TABLE + '#' + oldName,
|
||||
newId = this.COMPONENTS_TABLE + '#' + newName;
|
||||
const oldId = CoreLocalNotificationsProvider.COMPONENTS_TABLE + '#' + oldName;
|
||||
const newId = CoreLocalNotificationsProvider.COMPONENTS_TABLE + '#' + newName;
|
||||
|
||||
return this.appDB.updateRecords(this.COMPONENTS_TABLE, {id: newId}, {id: oldId});
|
||||
await this.appDB.updateRecords(CoreLocalNotificationsProvider.COMPONENTS_TABLE, { id: newId }, { id: oldId });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreLocalNotifications extends makeSingleton(CoreLocalNotificationsProvider) {}
|
||||
|
||||
export type CoreLocalNotificationsClickCallback<T = unknown> = (value: T) => void;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { FileEntry } from '@ionic-native/file';
|
||||
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreFilepool, CoreFilepoolOnProgressCallback } from '@services/filepool';
|
||||
import { CoreWSExternalFile } from '@services/ws';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||
|
@ -26,6 +26,7 @@ import { makeSingleton } from '@singletons/core.singletons';
|
|||
*/
|
||||
@Injectable()
|
||||
export class CorePluginFileDelegate extends CoreDelegate {
|
||||
|
||||
protected handlerNameProperty = 'component';
|
||||
|
||||
constructor() {
|
||||
|
@ -40,14 +41,12 @@ export class CorePluginFileDelegate extends CoreDelegate {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
fileDeleted(fileUrl: string, path: string, siteId?: string): Promise<any> {
|
||||
const handler = this.getHandlerForFile({fileurl: fileUrl});
|
||||
async fileDeleted(fileUrl: string, path: string, siteId?: string): Promise<void> {
|
||||
const handler = this.getHandlerForFile({ fileurl: fileUrl });
|
||||
|
||||
if (handler && handler.fileDeleted) {
|
||||
return handler.fileDeleted(fileUrl, path, siteId);
|
||||
await handler.fileDeleted(fileUrl, path, siteId);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,9 +70,8 @@ export class CorePluginFileDelegate extends CoreDelegate {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the file to use. Rejected if cannot download.
|
||||
*/
|
||||
protected async getHandlerDownloadableFile(file: CoreWSExternalFile, handler: CorePluginFileHandler, siteId?: string)
|
||||
: Promise<CoreWSExternalFile> {
|
||||
|
||||
protected async getHandlerDownloadableFile(file: CoreWSExternalFile, handler: CorePluginFileHandler, siteId?: string):
|
||||
Promise<CoreWSExternalFile> {
|
||||
const isDownloadable = await this.isFileDownloadable(file, siteId);
|
||||
|
||||
if (!isDownloadable.downloadable) {
|
||||
|
@ -132,7 +130,7 @@ export class CorePluginFileDelegate extends CoreDelegate {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with file size and a boolean to indicate if it is the total size or only partial.
|
||||
*/
|
||||
async getFilesDownloadSize(files: CoreWSExternalFile[], siteId?: string): Promise<{ size: number, total: boolean }> {
|
||||
async getFilesDownloadSize(files: CoreWSExternalFile[], siteId?: string): Promise<{ size: number; total: boolean }> {
|
||||
const filteredFiles = [];
|
||||
|
||||
await Promise.all(files.map(async (file) => {
|
||||
|
@ -153,10 +151,10 @@ export class CorePluginFileDelegate extends CoreDelegate {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with file size and a boolean to indicate if it is the total size or only partial.
|
||||
*/
|
||||
async getFilesSize(files: CoreWSExternalFile[], siteId?: string): Promise<{ size: number, total: boolean }> {
|
||||
async getFilesSize(files: CoreWSExternalFile[], siteId?: string): Promise<{ size: number; total: boolean }> {
|
||||
const result = {
|
||||
size: 0,
|
||||
total: true
|
||||
total: true,
|
||||
};
|
||||
|
||||
await Promise.all(files.map(async (file) => {
|
||||
|
@ -231,7 +229,7 @@ export class CorePluginFileDelegate extends CoreDelegate {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise with the data.
|
||||
*/
|
||||
isFileDownloadable(file: CoreWSExternalFile, siteId?: string): Promise<CorePluginFileDownloadableResult> {
|
||||
async isFileDownloadable(file: CoreWSExternalFile, siteId?: string): Promise<CorePluginFileDownloadableResult> {
|
||||
const handler = this.getHandlerForFile(file);
|
||||
|
||||
if (handler && handler.isFileDownloadable) {
|
||||
|
@ -239,7 +237,7 @@ export class CorePluginFileDelegate extends CoreDelegate {
|
|||
}
|
||||
|
||||
// Default to true.
|
||||
return Promise.resolve({downloadable: true});
|
||||
return { downloadable: true };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -272,15 +270,15 @@ export class CorePluginFileDelegate extends CoreDelegate {
|
|||
* @param onProgress Function to call on progress.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: (event: any) => any): Promise<any> {
|
||||
const handler = this.getHandlerForFile({fileurl: fileUrl});
|
||||
async treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: CoreFilepoolOnProgressCallback):
|
||||
Promise<void> {
|
||||
const handler = this.getHandlerForFile({ fileurl: fileUrl });
|
||||
|
||||
if (handler && handler.treatDownloadedFile) {
|
||||
return handler.treatDownloadedFile(fileUrl, file, siteId, onProgress);
|
||||
await handler.treatDownloadedFile(fileUrl, file, siteId, onProgress);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CorePluginFile extends makeSingleton(CorePluginFileDelegate) {}
|
||||
|
@ -320,7 +318,7 @@ export interface CorePluginFileHandler extends CoreDelegateHandler {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
fileDeleted?(fileUrl: string, path: string, siteId?: string): Promise<any>;
|
||||
fileDeleted?(fileUrl: string, path: string, siteId?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Check whether a file can be downloaded. If so, return the file to download.
|
||||
|
@ -375,7 +373,8 @@ export interface CorePluginFileHandler extends CoreDelegateHandler {
|
|||
* @param onProgress Function to call on progress.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: (event: any) => any): Promise<any>;
|
||||
treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string, onProgress?: CoreFilepoolOnProgressCallback):
|
||||
Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import * as moment from 'moment';
|
||||
import moment, { LongDateFormatKey } from 'moment';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
||||
|
||||
|
@ -24,7 +24,7 @@ import { makeSingleton, Translate } from '@singletons/core.singletons';
|
|||
@Injectable()
|
||||
export class CoreTimeUtilsProvider {
|
||||
|
||||
protected FORMAT_REPLACEMENTS = { // To convert PHP strf format to Moment format.
|
||||
protected static readonly FORMAT_REPLACEMENTS = { // To convert PHP strf format to Moment format.
|
||||
'%a': 'ddd',
|
||||
'%A': 'dddd',
|
||||
'%d': 'DD',
|
||||
|
@ -65,7 +65,7 @@ export class CoreTimeUtilsProvider {
|
|||
'%x': 'L',
|
||||
'%n': '\n',
|
||||
'%t': '\t',
|
||||
'%%': '%'
|
||||
'%%': '%',
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -97,7 +97,8 @@ export class CoreTimeUtilsProvider {
|
|||
converted += ']';
|
||||
}
|
||||
|
||||
converted += typeof this.FORMAT_REPLACEMENTS[char] != 'undefined' ? this.FORMAT_REPLACEMENTS[char] : char;
|
||||
converted += typeof CoreTimeUtilsProvider.FORMAT_REPLACEMENTS[char] != 'undefined' ?
|
||||
CoreTimeUtilsProvider.FORMAT_REPLACEMENTS[char] : char;
|
||||
} else {
|
||||
// Not a PHP format. We need to escape them, otherwise the letters could be confused with Moment formats.
|
||||
if (!escaping) {
|
||||
|
@ -129,7 +130,7 @@ export class CoreTimeUtilsProvider {
|
|||
}
|
||||
|
||||
// The component ion-datetime doesn't support escaping characters ([]), so we remove them.
|
||||
let fixed = format.replace(/[\[\]]/g, '');
|
||||
let fixed = format.replace(/[[\]]/g, '');
|
||||
|
||||
if (fixed.indexOf('A') != -1) {
|
||||
// Do not use am/pm format because there is a bug in ion-datetime.
|
||||
|
@ -250,7 +251,6 @@ export class CoreTimeUtilsProvider {
|
|||
* @return Duration in a short human readable format.
|
||||
*/
|
||||
formatDurationShort(duration: number): string {
|
||||
|
||||
const minutes = Math.floor(duration / 60);
|
||||
const seconds = duration - minutes * 60;
|
||||
const durations = [];
|
||||
|
@ -346,7 +346,7 @@ export class CoreTimeUtilsProvider {
|
|||
* @param localizedFormat Format to use.
|
||||
* @return Localized ISO format
|
||||
*/
|
||||
getLocalizedDateFormat(localizedFormat: any): string {
|
||||
getLocalizedDateFormat(localizedFormat: LongDateFormatKey): string {
|
||||
return moment.localeData().longDateFormat(localizedFormat);
|
||||
}
|
||||
|
||||
|
@ -366,6 +366,7 @@ export class CoreTimeUtilsProvider {
|
|||
return moment().startOf('day').unix();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreTimeUtils extends makeSingleton(CoreTimeUtilsProvider) {}
|
||||
|
|
|
@ -55,7 +55,7 @@ export class CoreUrlUtilsProvider {
|
|||
* @param boolToNumber Whether to convert bools to 1 or 0.
|
||||
* @return URL with params.
|
||||
*/
|
||||
addParamsToUrl(url: string, params?: {[key: string]: any}, anchor?: string, boolToNumber?: boolean): string {
|
||||
addParamsToUrl(url: string, params?: CoreUrlParams, anchor?: string, boolToNumber?: boolean): string {
|
||||
let separator = url.indexOf('?') != -1 ? '&' : '?';
|
||||
|
||||
for (const key in params) {
|
||||
|
@ -63,7 +63,7 @@ export class CoreUrlUtilsProvider {
|
|||
|
||||
if (boolToNumber && typeof value == 'boolean') {
|
||||
// Convert booleans to 1 or 0.
|
||||
value = value ? 1 : 0;
|
||||
value = value ? '1' : '0';
|
||||
}
|
||||
|
||||
// Ignore objects.
|
||||
|
@ -102,7 +102,7 @@ export class CoreUrlUtilsProvider {
|
|||
canUseTokenPluginFile(url: string, siteUrl: string, accessKey?: string): boolean {
|
||||
// Do not use tokenpluginfile if site doesn't use slash params, the URL doesn't work.
|
||||
// Also, only use it for "core" pluginfile endpoints. Some plugins can implement their own endpoint (like customcert).
|
||||
return accessKey && !url.match(/[\&?]file=/) && (
|
||||
return accessKey && !url.match(/[&?]file=/) && (
|
||||
url.indexOf(CoreTextUtils.instance.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 ||
|
||||
url.indexOf(CoreTextUtils.instance.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0);
|
||||
}
|
||||
|
@ -113,13 +113,13 @@ export class CoreUrlUtilsProvider {
|
|||
* @param url URL to treat.
|
||||
* @return Object with the params.
|
||||
*/
|
||||
extractUrlParams(url: string): any {
|
||||
extractUrlParams(url: string): CoreUrlParams {
|
||||
const regex = /[?&]+([^=&]+)=?([^&]*)?/gi;
|
||||
const subParamsPlaceholder = '@@@SUBPARAMS@@@';
|
||||
const params: any = {};
|
||||
const params: CoreUrlParams = {};
|
||||
const urlAndHash = url.split('#');
|
||||
const questionMarkSplit = urlAndHash[0].split('?');
|
||||
let subParams;
|
||||
let subParams: string;
|
||||
|
||||
if (questionMarkSplit.length > 2) {
|
||||
// There is more than one question mark in the URL. This can happen if any of the params is a URL with params.
|
||||
|
@ -190,10 +190,10 @@ export class CoreUrlUtilsProvider {
|
|||
url = url.replace('/pluginfile', '/webservice/pluginfile');
|
||||
}
|
||||
|
||||
url = this.addParamsToUrl(url, {token});
|
||||
url = this.addParamsToUrl(url, { token });
|
||||
}
|
||||
|
||||
return this.addParamsToUrl(url, {offline: 1}); // Always send offline=1 (it's for external repositories).
|
||||
return this.addParamsToUrl(url, { offline: '1' }); // Always send offline=1 (it's for external repositories).
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -206,7 +206,7 @@ export class CoreUrlUtilsProvider {
|
|||
url = url.trim();
|
||||
|
||||
// Check if the URL starts by http or https.
|
||||
if (! /^http(s)?\:\/\/.*/i.test(url)) {
|
||||
if (! /^http(s)?:\/\/.*/i.test(url)) {
|
||||
// Test first allways https.
|
||||
url = 'https://' + url;
|
||||
}
|
||||
|
@ -228,7 +228,7 @@ export class CoreUrlUtilsProvider {
|
|||
* @param page Docs page to go to.
|
||||
* @return Promise resolved with the Moodle docs URL.
|
||||
*/
|
||||
getDocsUrl(release?: string, page: string = 'Mobile_app'): Promise<string> {
|
||||
async getDocsUrl(release?: string, page: string = 'Mobile_app'): Promise<string> {
|
||||
let docsUrl = 'https://docs.moodle.org/en/' + page;
|
||||
|
||||
if (typeof release != 'undefined') {
|
||||
|
@ -240,11 +240,13 @@ export class CoreUrlUtilsProvider {
|
|||
}
|
||||
}
|
||||
|
||||
return CoreLang.instance.getCurrentLanguage().then((lang) => {
|
||||
try {
|
||||
const lang = await CoreLang.instance.getCurrentLanguage();
|
||||
|
||||
return docsUrl.replace('/en/', '/' + lang + '/');
|
||||
}).catch(() => {
|
||||
} catch (error) {
|
||||
return docsUrl;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -258,13 +260,13 @@ export class CoreUrlUtilsProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
let videoId;
|
||||
const params: any = {};
|
||||
let videoId: string;
|
||||
const params: CoreUrlParams = {};
|
||||
|
||||
url = CoreTextUtils.instance.decodeHTML(url);
|
||||
|
||||
// Get the video ID.
|
||||
let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/);
|
||||
let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/);
|
||||
|
||||
if (match && match[2].length === 11) {
|
||||
videoId = match[2];
|
||||
|
@ -276,7 +278,7 @@ export class CoreUrlUtilsProvider {
|
|||
}
|
||||
|
||||
// Now get the playlist (if any).
|
||||
match = url.match(/[?&]list=([^#\&\?]+)/);
|
||||
match = url.match(/[?&]list=([^#&?]+)/);
|
||||
|
||||
if (match && match[1]) {
|
||||
params.list = match[1];
|
||||
|
@ -286,13 +288,15 @@ export class CoreUrlUtilsProvider {
|
|||
match = url.match(/[?&]start=(\d+)/);
|
||||
|
||||
if (match && match[1]) {
|
||||
params.start = parseInt(match[1], 10);
|
||||
params.start = parseInt(match[1], 10).toString();
|
||||
} else {
|
||||
// No start param, but it could have a time param.
|
||||
match = url.match(/[?&]t=(\d+h)?(\d+m)?(\d+s)?/);
|
||||
if (match) {
|
||||
params.start = (match[1] ? parseInt(match[1], 10) * 3600 : 0) + (match[2] ? parseInt(match[2], 10) * 60 : 0) +
|
||||
(match[3] ? parseInt(match[3], 10) : 0);
|
||||
const start = (match[1] ? parseInt(match[1], 10) * 3600 : 0) +
|
||||
(match[2] ? parseInt(match[2], 10) * 60 : 0) +
|
||||
(match[3] ? parseInt(match[3], 10) : 0);
|
||||
params.start = start.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,7 +332,7 @@ export class CoreUrlUtilsProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
const matches = url.match(/^([^\/:\.\?]*):\/\//);
|
||||
const matches = url.match(/^([^/:.?]*):\/\//);
|
||||
if (matches && matches[1]) {
|
||||
return matches[1];
|
||||
}
|
||||
|
@ -361,11 +365,11 @@ export class CoreUrlUtilsProvider {
|
|||
getUsernameFromUrl(url: string): string {
|
||||
if (url.indexOf('@') > -1) {
|
||||
// Get URL without protocol.
|
||||
const withoutProtocol = url.replace(/^[^?@\/]*:\/\//, '');
|
||||
const withoutProtocol = url.replace(/^[^?@/]*:\/\//, '');
|
||||
const matches = withoutProtocol.match(/[^@]*/);
|
||||
|
||||
// Make sure that @ is at the start of the URL, not in a param at the end.
|
||||
if (matches && matches.length && !matches[0].match(/[\/|?]/)) {
|
||||
if (matches && matches.length && !matches[0].match(/[/|?]/)) {
|
||||
return matches[0];
|
||||
}
|
||||
}
|
||||
|
@ -408,7 +412,7 @@ export class CoreUrlUtilsProvider {
|
|||
* @return Whether the url uses http or https protocol.
|
||||
*/
|
||||
isHttpURL(url: string): boolean {
|
||||
return /^https?\:\/\/.+/i.test(url);
|
||||
return /^https?:\/\/.+/i.test(url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -427,10 +431,11 @@ export class CoreUrlUtilsProvider {
|
|||
* Check whether a URL scheme belongs to a local file.
|
||||
*
|
||||
* @param scheme Scheme to check.
|
||||
* @param domain The domain. Needed because in Android the WebView scheme is http.
|
||||
* @param notUsed Unused parameter.
|
||||
* @return Whether the scheme belongs to a local file.
|
||||
*/
|
||||
isLocalFileUrlScheme(scheme: string, domain: string): boolean {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
isLocalFileUrlScheme(scheme: string, notUsed?: string): boolean {
|
||||
if (scheme) {
|
||||
scheme = scheme.toLowerCase();
|
||||
}
|
||||
|
@ -483,7 +488,7 @@ export class CoreUrlUtilsProvider {
|
|||
* @return URL without params.
|
||||
*/
|
||||
removeUrlParams(url: string): string {
|
||||
const matches = url.match(/^[^\?]+/);
|
||||
const matches = url.match(/^[^?]+/);
|
||||
|
||||
return matches && matches[0];
|
||||
}
|
||||
|
@ -515,6 +520,9 @@ export class CoreUrlUtilsProvider {
|
|||
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreUrlUtils extends makeSingleton(CoreUrlUtilsProvider) {}
|
||||
|
||||
export type CoreUrlParams = {[key: string]: string};
|
||||
|
|
|
@ -27,12 +27,18 @@ import { environment } from '@/environments/environment';
|
|||
* Then you can call the log function you want to use in this logger instance.
|
||||
*/
|
||||
export class CoreLogger {
|
||||
|
||||
log: LogFunction;
|
||||
info: LogFunction;
|
||||
warn: LogFunction;
|
||||
debug: LogFunction;
|
||||
error: LogFunction;
|
||||
|
||||
// Avoid creating singleton instances.
|
||||
private constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a logger instance for a certain class, service or component.
|
||||
*
|
||||
|
@ -88,6 +94,7 @@ export class CoreLogger {
|
|||
logFn.apply(null, args);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -72,7 +72,9 @@ interface UrlParts {
|
|||
export class CoreUrl {
|
||||
|
||||
// Avoid creating singleton instances.
|
||||
private constructor() {}
|
||||
private constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse parts of a url, using an implicit protocol if it is missing from the url.
|
||||
|
@ -123,14 +125,14 @@ export class CoreUrl {
|
|||
|
||||
// Match using common suffixes.
|
||||
const knownSuffixes = [
|
||||
'\/my\/?',
|
||||
'\/\\\?redirect=0',
|
||||
'\/index\\\.php',
|
||||
'\/course\/view\\\.php',
|
||||
'\/login\/index\\\.php',
|
||||
'\/mod\/page\/view\\\.php',
|
||||
'/my/?',
|
||||
'/\\?redirect=0',
|
||||
'/index\\.php',
|
||||
'/course/view\\.php',
|
||||
'\\/login/index\\.php',
|
||||
'/mod/page/view\\.php',
|
||||
];
|
||||
const match = url.match(new RegExp(`^https?:\/\/(.*?)(${knownSuffixes.join('|')})`));
|
||||
const match = url.match(new RegExp(`^https?://(.*?)(${knownSuffixes.join('|')})`));
|
||||
|
||||
if (match) {
|
||||
return match[1];
|
||||
|
@ -184,10 +186,10 @@ export class CoreUrl {
|
|||
*/
|
||||
static sameDomainAndPath(urlA: string, urlB: string): boolean {
|
||||
// Add protocol if missing, the parse function requires it.
|
||||
if (!urlA.match(/^[^\/:\.\?]*:\/\//)) {
|
||||
if (!urlA.match(/^[^/:.?]*:\/\//)) {
|
||||
urlA = `https://${urlA}`;
|
||||
}
|
||||
if (!urlB.match(/^[^\/:\.\?]*:\/\//)) {
|
||||
if (!urlB.match(/^[^/:.?]*:\/\//)) {
|
||||
urlB = `https://${urlB}`;
|
||||
}
|
||||
|
||||
|
@ -197,4 +199,5 @@ export class CoreUrl {
|
|||
return partsA.domain == partsB.domain &&
|
||||
CoreTextUtils.instance.removeEndingSlash(partsA.path) == CoreTextUtils.instance.removeEndingSlash(partsB.path);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,11 @@ export type CoreWindowOpenOptions = {
|
|||
*/
|
||||
export class CoreWindow {
|
||||
|
||||
// Avoid creating singleton instances.
|
||||
private constructor() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* "Safe" implementation of window.open. It will open the URL without overriding the app.
|
||||
*
|
||||
|
@ -73,4 +78,5 @@ export class CoreWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -9,6 +9,7 @@
|
|||
* https://ionicframework.com/docs/layout/global-stylesheets
|
||||
*/
|
||||
|
||||
|
||||
/* Core CSS required for Ionic components to work properly */
|
||||
@import "~@ionic/angular/css/core.css";
|
||||
|
||||
|
@ -24,3 +25,6 @@
|
|||
@import "~@ionic/angular/css/text-alignment.css";
|
||||
@import "~@ionic/angular/css/text-transformation.css";
|
||||
@import "~@ionic/angular/css/flex-utils.css";
|
||||
|
||||
/* Font awesome */
|
||||
@import "~font-awesome/scss/font-awesome.scss";
|
||||
|
|
|
@ -24,3 +24,10 @@ declare global {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Course base definition.
|
||||
*/
|
||||
export type CoreCourseBase = {
|
||||
id: number; // Course Id.
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue