From 1652e4d8f2e713eec23e050c7c53e67d6049bb03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 29 Sep 2020 14:21:47 +0200 Subject: [PATCH 1/5] MOBILE-3200 database: Fix update access data on group change --- src/addon/mod/data/components/index/index.ts | 137 ++++++++++--------- 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/src/addon/mod/data/components/index/index.ts b/src/addon/mod/data/components/index/index.ts index 34feeef8f..1df731973 100644 --- a/src/addon/mod/data/components/index/index.ts +++ b/src/addon/mod/data/components/index/index.ts @@ -180,69 +180,67 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp * @param showErrors If show errors to the user of hide them. * @return Promise resolved when done. */ - protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { + protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { let canAdd = false, canSearch = false; - return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => { - this.data = data; - this.hasComments = data.comments; + this.data = await this.dataProvider.getDatabase(this.courseId, this.module.id); + this.hasComments = this.data.comments; - this.description = data.intro || data.description; - this.dataRetrieved.emit(data); + this.description = this.data.intro || this.data.description; + this.dataRetrieved.emit(this.data); - if (sync) { + if (sync) { + try { // Try to synchronize the data. - return this.syncActivity(showErrors).catch(() => { - // Ignore errors. - }); + await this.syncActivity(showErrors); + } catch (error) { + // Ignore errors. } - }).then(() => { - return this.dataProvider.getDatabaseAccessInformation(this.data.id, {cmId: this.module.id}); - }).then((accessData) => { - this.access = accessData; + } - if (!accessData.timeavailable) { - const time = this.timeUtils.timestamp(); + this.groupInfo = await this.groupsProvider.getActivityGroupInfo(this.data.coursemodule); + this.selectedGroup = this.groupsProvider.validateGroupId(this.selectedGroup, this.groupInfo); - this.timeAvailableFrom = this.data.timeavailablefrom && time < this.data.timeavailablefrom ? - parseInt(this.data.timeavailablefrom, 10) * 1000 : false; - this.timeAvailableFromReadable = this.timeAvailableFrom ? this.timeUtils.userDate(this.timeAvailableFrom) : false; - this.timeAvailableTo = this.data.timeavailableto && time > this.data.timeavailableto ? - parseInt(this.data.timeavailableto, 10) * 1000 : false; - this.timeAvailableToReadable = this.timeAvailableTo ? this.timeUtils.userDate(this.timeAvailableTo) : false; + this.access = await this.dataProvider.getDatabaseAccessInformation(this.data.id, { + cmId: this.module.id, + groupId: this.selectedGroup || undefined + }); - this.isEmpty = true; - this.groupInfo = null; + if (!this.access.timeavailable) { + const time = this.timeUtils.timestamp(); - return; - } + this.timeAvailableFrom = this.data.timeavailablefrom && time < this.data.timeavailablefrom ? + parseInt(this.data.timeavailablefrom, 10) * 1000 : false; + this.timeAvailableFromReadable = this.timeAvailableFrom ? this.timeUtils.userDate(this.timeAvailableFrom) : false; + this.timeAvailableTo = this.data.timeavailableto && time > this.data.timeavailableto ? + parseInt(this.data.timeavailableto, 10) * 1000 : false; + this.timeAvailableToReadable = this.timeAvailableTo ? this.timeUtils.userDate(this.timeAvailableTo) : false; + this.isEmpty = true; + this.groupInfo = null; + } else { canSearch = true; - canAdd = accessData.canaddentry; + canAdd = this.access.canaddentry; + } - return this.groupsProvider.getActivityGroupInfo(this.data.coursemodule).then((groupInfo) => { - this.groupInfo = groupInfo; - this.selectedGroup = this.groupsProvider.validateGroupId(this.selectedGroup, groupInfo); - }); - }).then(() => { - return this.dataProvider.getFields(this.data.id, {cmId: this.module.id}).then((fields) => { - if (fields.length == 0) { - canSearch = false; - canAdd = false; - } - this.search.advanced = []; + const fields = await this.dataProvider.getFields(this.data.id, {cmId: this.module.id}); + this.search.advanced = []; - this.fields = this.utils.arrayToObject(fields, 'id'); - this.fieldsArray = this.utils.objectToArray(this.fields); + this.fields = this.utils.arrayToObject(fields, 'id'); + this.fieldsArray = this.utils.objectToArray(this.fields); + if (this.fieldsArray.length == 0) { + canSearch = false; + canAdd = false; + } - return this.fetchEntriesData(); - }); - }).finally(() => { + try { + await this.fetchEntriesData(); + } finally { this.canAdd = canAdd; this.canSearch = canSearch; this.fillContextMenu(refresh); - }); + } } /** @@ -252,24 +250,16 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp */ protected fetchEntriesData(): Promise { - return this.dataProvider.getDatabaseAccessInformation(this.data.id, { + const search = this.search.searching && !this.search.searchingAdvanced ? this.search.text : undefined; + const advSearch = this.search.searching && this.search.searchingAdvanced ? this.search.advanced : undefined; + + return this.dataHelper.fetchEntries(this.data, this.fieldsArray, { groupId: this.selectedGroup, - cmId: this.module.id, - }).then((accessData) => { - // Update values for current group. - this.access.canaddentry = accessData.canaddentry; - - const search = this.search.searching && !this.search.searchingAdvanced ? this.search.text : undefined; - const advSearch = this.search.searching && this.search.searchingAdvanced ? this.search.advanced : undefined; - - return this.dataHelper.fetchEntries(this.data, this.fieldsArray, { - groupId: this.selectedGroup, - search, - advSearch, - sort: Number(this.search.sortBy), - order: this.search.sortDirection, - page: this.search.page, - }); + search, + advSearch, + sort: Number(this.search.sortBy), + order: this.search.sortDirection, + page: this.search.page, }).then((entries) => { const numEntries = entries.entries.length; const numOfflineEntries = entries.offlineEntries.length; @@ -390,18 +380,29 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp * @param groupId Group ID. * @return Resolved when new group is selected or rejected if not. */ - setGroup(groupId: number): Promise { + async setGroup(groupId: number): Promise { this.selectedGroup = groupId; this.search.page = 0; - return this.fetchEntriesData().then(() => { + // Only update canAdd if there's any field, otheerwise, canAdd will remain false. + if (this.fieldsArray.length > 0) { + // Update values for current group. + this.access = await this.dataProvider.getDatabaseAccessInformation(this.data.id, { + groupId: this.selectedGroup, + cmId: this.module.id, + }); + + this.canAdd = this.access.canaddentry; + } + + try { + await this.fetchEntriesData(); + // Log activity view for coherence with Moodle web. return this.logView(); - }).catch((message) => { - this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true); - - return Promise.reject(null); - }); + } catch (error) { + this.domUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); + } } /** From 6d7a96d8dfb3006bd0e102b01ac4f88f00380768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 29 Sep 2020 14:40:51 +0200 Subject: [PATCH 2/5] MOBILE-3200 database: Do not need courseId to delete an offline entry --- src/addon/mod/data/providers/helper.ts | 73 ++++++++++++++++---------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/src/addon/mod/data/providers/helper.ts b/src/addon/mod/data/providers/helper.ts index ea686e422..eaf9a2b5d 100644 --- a/src/addon/mod/data/providers/helper.ts +++ b/src/addon/mod/data/providers/helper.ts @@ -18,7 +18,6 @@ import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider } from '@providers/utils/text'; -import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { AddonModDataFieldsDelegate } from './fields-delegate'; @@ -35,12 +34,19 @@ import { CoreRatingOfflineProvider } from '@core/rating/providers/offline'; @Injectable() export class AddonModDataHelperProvider { - constructor(private sitesProvider: CoreSitesProvider, protected dataProvider: AddonModDataProvider, - private translate: TranslateService, private fieldsDelegate: AddonModDataFieldsDelegate, - private dataOffline: AddonModDataOfflineProvider, private fileUploaderProvider: CoreFileUploaderProvider, - private textUtils: CoreTextUtilsProvider, private eventsProvider: CoreEventsProvider, private utils: CoreUtilsProvider, - private domUtils: CoreDomUtilsProvider, private courseProvider: CoreCourseProvider, - private ratingOffline: CoreRatingOfflineProvider) {} + constructor( + protected sitesProvider: CoreSitesProvider, + protected dataProvider: AddonModDataProvider, + protected translate: TranslateService, + protected fieldsDelegate: AddonModDataFieldsDelegate, + protected dataOffline: AddonModDataOfflineProvider, + protected fileUploaderProvider: CoreFileUploaderProvider, + protected textUtils: CoreTextUtilsProvider, + protected eventsProvider: CoreEventsProvider, + protected domUtils: CoreDomUtilsProvider, + protected courseProvider: CoreCourseProvider, + protected ratingOffline: CoreRatingOfflineProvider + ) {} /** * Returns the record with the offline actions applied. @@ -632,35 +638,44 @@ export class AddonModDataHelperProvider { * @param courseId Course ID. It not defined, it will be fetched. * @param siteId Site ID. If not defined, current site. */ - showDeleteEntryModal(dataId: number, entryId: number, courseId?: number, siteId?: string): void { + async showDeleteEntryModal(dataId: number, entryId: number, courseId?: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - this.domUtils.showDeleteConfirm('addon.mod_data.confirmdeleterecord').then(() => { - const modal = this.domUtils.showModalLoading(); + let modal; + try { + await this.domUtils.showDeleteConfirm('addon.mod_data.confirmdeleterecord'); - return this.getActivityCourseIdIfNotSet(dataId, courseId, siteId).then((courseId) => { - return this.dataProvider.deleteEntry(dataId, entryId, courseId, siteId); - }).catch((message) => { + modal = this.domUtils.showModalLoading(); + + try { + if (entryId > 0) { + courseId = await this.getActivityCourseIdIfNotSet(dataId, courseId, siteId); + } + + this.dataProvider.deleteEntry(dataId, entryId, courseId, siteId); + } catch (message) { this.domUtils.showErrorModalDefault(message, 'addon.mod_data.errordeleting', true); - return Promise.reject(null); - }).then(() => { - return this.utils.allPromises([ - this.dataProvider.invalidateEntryData(dataId, entryId, siteId), - this.dataProvider.invalidateEntriesData(dataId, siteId) - ]).catch(() => { - // Ignore errors. - }); - }).then(() => { - this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId, entryId, deleted: true}, siteId); + modal && modal.dismiss(); - this.domUtils.showToast('addon.mod_data.recorddeleted', true, 3000); - }).finally(() => { - modal.dismiss(); - }); - }).catch(() => { + return; + } + + try { + await this.dataProvider.invalidateEntryData(dataId, entryId, siteId); + await this.dataProvider.invalidateEntriesData(dataId, siteId); + } catch (error) { + // Ignore errors. + } + + this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId, entryId, deleted: true}, siteId); + + this.domUtils.showToast('addon.mod_data.recorddeleted', true, 3000); + } catch (error) { // Ignore error, it was already displayed. - }); + } + + modal && modal.dismiss(); } /** From b0b806280b8fd1de62db0ff6f53473dfba92c974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 29 Sep 2020 15:39:39 +0200 Subject: [PATCH 3/5] MOBILE-3200 database: Improve add/edit offline action handling --- src/addon/mod/data/pages/edit/edit.ts | 33 +++++++++------ src/addon/mod/data/providers/data.ts | 61 ++++++++++++++------------- 2 files changed, 51 insertions(+), 43 deletions(-) diff --git a/src/addon/mod/data/pages/edit/edit.ts b/src/addon/mod/data/pages/edit/edit.ts index b3542eebf..72e56a5ac 100644 --- a/src/addon/mod/data/pages/edit/edit.ts +++ b/src/addon/mod/data/pages/edit/edit.ts @@ -52,6 +52,8 @@ export class AddonModDataEditPage { protected siteId: string; protected offline: boolean; protected forceLeave = false; // To allow leaving the page without checking for changes. + protected initialSelectedGroup = null; + protected isEditing = false; title = ''; component = AddonModDataProvider.COMPONENT; @@ -75,7 +77,10 @@ export class AddonModDataEditPage { this.module = params.get('module') || {}; this.entryId = params.get('entryId') || null; this.courseId = params.get('courseId'); - this.selectedGroup = params.get('group') || 0; + this.selectedGroup = this.entryId ? null : (params.get('group') || 0); + + // If entryId is lower than 0 or null, it is a new entry or an offline entry. + this.isEditing = this.entryId && this.entryId > 0; this.siteId = sitesProvider.getCurrentSiteId(); @@ -103,7 +108,8 @@ export class AddonModDataEditPage { const inputData = this.editForm.value; - const changed = await this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id, this.entry.contents); + let changed = await this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id, this.entry.contents); + changed = changed || (!this.isEditing && this.initialSelectedGroup != this.selectedGroup); if (changed) { // Show confirmation if some data has been modified. @@ -169,6 +175,7 @@ export class AddonModDataEditPage { return this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id, this.entry.contents).then((changed) => { + changed = changed || (!this.isEditing && this.initialSelectedGroup != this.selectedGroup); if (!changed) { if (this.entryId) { return this.returnToEntryList(); @@ -196,7 +203,7 @@ export class AddonModDataEditPage { return Promise.reject(e); }).then((editData) => { if (editData.length > 0) { - if (this.entryId) { + if (this.isEditing) { return this.dataProvider.editEntry(this.data.id, this.entryId, this.courseId, editData, this.fields, undefined, this.offline); } @@ -213,20 +220,20 @@ export class AddonModDataEditPage { } // This is done if entry is updated when editing or creating if not. - if ((this.entryId && result.updated) || (!this.entryId && result.newentryid)) { + if ((this.isEditing && result.updated) || (!this.isEditing && result.newentryid)) { this.domUtils.triggerFormSubmittedEvent(this.formElement, result.sent, this.siteId); - if (result.sent) { - this.eventsProvider.trigger(CoreEventsProvider.ACTIVITY_DATA_SENT, { module: 'data' }); - } - const promises = []; - this.entryId = this.entryId || result.newentryid; + if (result.sent) { + this.eventsProvider.trigger(CoreEventsProvider.ACTIVITY_DATA_SENT, { module: 'data' }); - promises.push(this.dataProvider.invalidateEntryData(this.data.id, this.entryId, this.siteId)); - promises.push(this.dataProvider.invalidateEntriesData(this.data.id, this.siteId)); + if (this.isEditing) { + promises.push(this.dataProvider.invalidateEntryData(this.data.id, this.entryId, this.siteId)); + } + promises.push(this.dataProvider.invalidateEntriesData(this.data.id, this.siteId)); + } return Promise.all(promises).then(() => { this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, @@ -264,7 +271,7 @@ export class AddonModDataEditPage { * @param groupId Group identifier to set. * @return Resolved when done. */ - setGroup(groupId: number): Promise { + setGroup(groupId: number): Promise { this.selectedGroup = groupId; this.loaded = false; @@ -322,7 +329,7 @@ export class AddonModDataEditPage { * * @return Resolved when done. */ - protected returnToEntryList(): Promise { + protected returnToEntryList(): Promise { const inputData = this.editForm.value; return this.dataHelper.getEditTmpFiles(inputData, this.fieldsArray, this.data.id, diff --git a/src/addon/mod/data/providers/data.ts b/src/addon/mod/data/providers/data.ts index cde86d294..6fd85d2db 100644 --- a/src/addon/mod/data/providers/data.ts +++ b/src/addon/mod/data/providers/data.ts @@ -133,6 +133,7 @@ export class AddonModDataProvider { }); }; + // Checks to store offline. if (!this.appProvider.isOnline() || forceOffline) { const notifications = this.checkFields(fields, contents); if (notifications) { @@ -140,22 +141,40 @@ export class AddonModDataProvider { fieldnotifications: notifications }); } - - return storeOffline(); } - return this.addEntryOnline(dataId, contents, groupId, siteId).then((result) => { - result.sent = true; + // Get other not synced actions. + return this.dataOffline.getEntryActions(dataId, entryId, siteId).then((entries) => { + if (entries && entries.length) { + // Found. Delete add and edit actions first. + const proms = []; + entries.forEach((entry) => { + if (entry.action == 'add') { + proms.push(this.dataOffline.deleteEntry(dataId, entryId, entry.action, siteId)); + } + }); - return result; - }).catch((error) => { - if (this.utils.isWebServiceError(error)) { - // The WebService has thrown an error, this means that responses cannot be submitted. - return Promise.reject(error); + return Promise.all(proms); + } + }).then(() => { + // App is offline, store the action. + if (!this.appProvider.isOnline() || forceOffline) { + return storeOffline(); } - // Couldn't connect to server, store in offline. - return storeOffline(); + return this.addEntryOnline(dataId, contents, groupId, siteId).then((result) => { + result.sent = true; + + return result; + }).catch((error) => { + if (this.utils.isWebServiceError(error)) { + // The WebService has thrown an error, this means that responses cannot be submitted. + return Promise.reject(error); + } + + // Couldn't connect to server, store in offline. + return storeOffline(); + }); }); } @@ -398,9 +417,6 @@ export class AddonModDataProvider { }); }; - let justAdded = false, - groupId; - if (!this.appProvider.isOnline() || forceOffline) { const notifications = this.checkFields(fields, contents); if (notifications) { @@ -416,11 +432,7 @@ export class AddonModDataProvider { // Found. Delete add and edit actions first. const proms = []; entries.forEach((entry) => { - if (entry.action == 'add') { - justAdded = true; - groupId = entry.groupid; - proms.push(this.dataOffline.deleteEntry(dataId, entryId, entry.action, siteId)); - } else if (entry.action == 'edit') { + if (entry.action == 'edit') { proms.push(this.dataOffline.deleteEntry(dataId, entryId, entry.action, siteId)); } }); @@ -428,17 +440,6 @@ export class AddonModDataProvider { return Promise.all(proms); } }).then(() => { - if (justAdded) { - // The field was added offline, add again and stop. - return this.addEntry(dataId, entryId, courseId, contents, groupId, fields, siteId, forceOffline) - .then((result) => { - result.updated = true; - result.sent = true; - - return result; - }); - } - if (!this.appProvider.isOnline() || forceOffline) { // App is offline, store the action. return storeOffline(); From cb4eac9a17c50dbdb3cb78dd3c7c5bb04e367ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 29 Sep 2020 15:40:27 +0200 Subject: [PATCH 4/5] MOBILE-3200 database: Check permissions on add and edit entry --- scripts/langindex.json | 2 + src/addon/mod/data/lang/en.json | 1 + src/addon/mod/data/pages/edit/edit.html | 4 +- src/addon/mod/data/pages/edit/edit.ts | 92 ++++++++++++++++++------- src/assets/lang/en.json | 1 + 5 files changed, 72 insertions(+), 28 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 2c1b0fd02..1e1606b32 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -507,11 +507,13 @@ "addon.mod_data.foundrecords": "data", "addon.mod_data.gettinglocation": "local_moodlemobileapp", "addon.mod_data.latlongboth": "data", + "addon.mod_data.locationnotenabled": "local_moodlemobileapp", "addon.mod_data.locationpermissiondenied": "local_moodlemobileapp", "addon.mod_data.menuchoose": "data", "addon.mod_data.modulenameplural": "data", "addon.mod_data.more": "data", "addon.mod_data.mylocation": "local_moodlemobileapp", + "addon.mod_data.noaccess": "data", "addon.mod_data.nomatch": "data", "addon.mod_data.norecords": "data", "addon.mod_data.notapproved": "data", diff --git a/src/addon/mod/data/lang/en.json b/src/addon/mod/data/lang/en.json index 106896f4f..920d6f014 100644 --- a/src/addon/mod/data/lang/en.json +++ b/src/addon/mod/data/lang/en.json @@ -28,6 +28,7 @@ "modulenameplural": "Databases", "more": "More", "mylocation": "My location", + "noaccess": "You do not have access to this page", "nomatch": "No matching entries found!", "norecords": "No entries in database", "notapproved": "Entry is not approved yet.", diff --git a/src/addon/mod/data/pages/edit/edit.html b/src/addon/mod/data/pages/edit/edit.html index 7b5418da5..557f9a175 100644 --- a/src/addon/mod/data/pages/edit/edit.html +++ b/src/addon/mod/data/pages/edit/edit.html @@ -18,8 +18,8 @@ -
- +
+
diff --git a/src/addon/mod/data/pages/edit/edit.ts b/src/addon/mod/data/pages/edit/edit.ts index 72e56a5ac..2a911e615 100644 --- a/src/addon/mod/data/pages/edit/edit.ts +++ b/src/addon/mod/data/pages/edit/edit.ts @@ -93,7 +93,7 @@ export class AddonModDataEditPage { * View loaded. */ ionViewDidLoad(): void { - this.fetchEntryData(); + this.fetchEntryData(true); } /** @@ -126,38 +126,78 @@ export class AddonModDataEditPage { /** * Fetch the entry data. * + * @param [refresh] To refresh all downloaded data. * @return Resolved when done. */ - protected fetchEntryData(): Promise { - return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => { - this.title = data.name || this.title; - this.data = data; - this.cssClass = 'addon-data-entries-' + data.id; + protected async fetchEntryData(refresh: boolean = false): Promise { + try { + this.data = await this.dataProvider.getDatabase(this.courseId, this.module.id); + this.title = this.data.name || this.title; + this.cssClass = 'addon-data-entries-' + this.data.id; - return this.dataProvider.getDatabaseAccessInformation(data.id, {cmId: this.module.id}); - }).then((accessData) => { - if (this.entryId) { - return this.groupsProvider.getActivityGroupInfo(this.data.coursemodule).then((groupInfo) => { - this.groupInfo = groupInfo; - this.selectedGroup = this.groupsProvider.validateGroupId(this.selectedGroup, groupInfo); - }); - } - }).then(() => { - return this.dataProvider.getFields(this.data.id, {cmId: this.module.id}); - }).then((fieldsData) => { - this.fieldsArray = fieldsData; - this.fields = this.utils.arrayToObject(fieldsData, 'id'); + this.fieldsArray = await this.dataProvider.getFields(this.data.id, {cmId: this.module.id}); + this.fields = this.utils.arrayToObject(this.fieldsArray, 'id'); + + const entry = await this.dataHelper.fetchEntry(this.data, this.fieldsArray, this.entryId); - return this.dataHelper.fetchEntry(this.data, fieldsData, this.entryId); - }).then((entry) => { this.entry = entry.entry; + // Load correct group. + this.selectedGroup = this.selectedGroup == null ? this.entry.groupid : this.selectedGroup; + + // Check permissions when adding a new entry or offline entry. + if (!this.isEditing) { + let haveAccess = false; + + if (refresh) { + this.groupInfo = await this.groupsProvider.getActivityGroupInfo(this.data.coursemodule); + this.selectedGroup = this.groupsProvider.validateGroupId(this.selectedGroup, this.groupInfo); + this.initialSelectedGroup = this.selectedGroup; + } + + if (this.groupInfo.groups.length > 0) { + if (refresh) { + const canAddGroup = {}; + + await Promise.all(this.groupInfo.groups.map(async (group) => { + const accessData = await this.dataProvider.getDatabaseAccessInformation(this.data.id, { + cmId: this.module.id, groupId: group.id}); + + canAddGroup[group.id] = accessData.canaddentry; + })); + + this.groupInfo.groups = this.groupInfo.groups.filter((group) => { + return !!canAddGroup[group.id]; + }); + + haveAccess = canAddGroup[this.selectedGroup]; + } else { + // Groups already filtered, so it have access. + haveAccess = true; + } + } else { + const accessData = await this.dataProvider.getDatabaseAccessInformation(this.data.id, {cmId: this.module.id}); + haveAccess = accessData.canaddentry; + } + + if (!haveAccess) { + // You shall not pass, go back. + this.domUtils.showErrorModal('addon.mod_data.noaccess', true); + + // Go back to entry list. + this.forceLeave = true; + this.navCtrl.pop(); + + return; + } + } + this.editFormRender = this.displayEditFields(); - }).catch((message) => { + } catch (message) { this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true); - }).finally(() => { - this.loaded = true; - }); + } + + this.loaded = true; } /** @@ -166,7 +206,7 @@ export class AddonModDataEditPage { * @param e Event. * @return Resolved when done. */ - save(e: Event): Promise { + save(e: Event): Promise { e.preventDefault(); e.stopPropagation(); diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index dedb0a4f6..dfc7d26d2 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -513,6 +513,7 @@ "addon.mod_data.modulenameplural": "Databases", "addon.mod_data.more": "More", "addon.mod_data.mylocation": "My location", + "addon.mod_data.noaccess": "You do not have access to this page", "addon.mod_data.nomatch": "No matching entries found!", "addon.mod_data.norecords": "No entries in database", "addon.mod_data.notapproved": "Entry is not approved yet.", From 572e907ac1e44d6828e1b4690c07f458d860fe94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 2 Oct 2020 10:26:08 +0200 Subject: [PATCH 5/5] MOBILE-3200 database: Translate some async functions --- src/addon/mod/data/providers/data.ts | 301 +++++++++++++-------------- 1 file changed, 141 insertions(+), 160 deletions(-) diff --git a/src/addon/mod/data/providers/data.ts b/src/addon/mod/data/providers/data.ts index 6fd85d2db..414249033 100644 --- a/src/addon/mod/data/providers/data.ts +++ b/src/addon/mod/data/providers/data.ts @@ -117,65 +117,51 @@ export class AddonModDataProvider { * @param forceOffline Force editing entry in offline. * @return Promise resolved when the action is done. */ - addEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], groupId: number = 0, + async addEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], groupId: number = 0, fields: any, siteId?: string, forceOffline: boolean = false): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); // Convenience function to store a data to be synchronized later. - const storeOffline = (): Promise => { - return this.dataOffline.saveEntry(dataId, entryId, 'add', courseId, groupId, contents, undefined, siteId) - .then((entry) => { - return { - // Return provissional entry Id. - newentryid: entry, - sent: false, - }; - }); + const storeOffline = async (): Promise => { + const entry = await this.dataOffline.saveEntry(dataId, entryId, 'add', courseId, groupId, contents, undefined, siteId); + + return { + // Return provissional entry Id. + newentryid: entry, + sent: false, + }; }; // Checks to store offline. if (!this.appProvider.isOnline() || forceOffline) { const notifications = this.checkFields(fields, contents); if (notifications) { - return Promise.resolve({ - fieldnotifications: notifications - }); + return { fieldnotifications: notifications }; } } - // Get other not synced actions. - return this.dataOffline.getEntryActions(dataId, entryId, siteId).then((entries) => { - if (entries && entries.length) { - // Found. Delete add and edit actions first. - const proms = []; - entries.forEach((entry) => { - if (entry.action == 'add') { - proms.push(this.dataOffline.deleteEntry(dataId, entryId, entry.action, siteId)); - } - }); + // Remove unnecessary not synced actions. + await this.deleteEntryOfflineAction(dataId, entryId, 'add', siteId); - return Promise.all(proms); - } - }).then(() => { - // App is offline, store the action. - if (!this.appProvider.isOnline() || forceOffline) { - return storeOffline(); + // App is offline, store the action. + if (!this.appProvider.isOnline() || forceOffline) { + return storeOffline(); + } + + try { + const result = await this.addEntryOnline(dataId, contents, groupId, siteId); + result.sent = true; + + return result; + } catch (error) { + if (this.utils.isWebServiceError(error)) { + // The WebService has thrown an error, this means that responses cannot be submitted. + throw error; } - return this.addEntryOnline(dataId, contents, groupId, siteId).then((result) => { - result.sent = true; - - return result; - }).catch((error) => { - if (this.utils.isWebServiceError(error)) { - // The WebService has thrown an error, this means that responses cannot be submitted. - return Promise.reject(error); - } - - // Couldn't connect to server, store in offline. - return storeOffline(); - }); - }); + // Couldn't connect to server, store in offline. + return storeOffline(); + } } /** @@ -212,48 +198,49 @@ export class AddonModDataProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the action is done. */ - approveEntry(dataId: number, entryId: number, approve: boolean, courseId: number, siteId?: string): Promise { + async approveEntry(dataId: number, entryId: number, approve: boolean, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); // Convenience function to store a data to be synchronized later. - const storeOffline = (): Promise => { + const storeOffline = async (): Promise => { const action = approve ? 'approve' : 'disapprove'; - return this.dataOffline.saveEntry(dataId, entryId, action, courseId, undefined, undefined, undefined, siteId) - .then(() => { - return { - sent: false, - }; - }); + await this.dataOffline.saveEntry(dataId, entryId, action, courseId, undefined, undefined, undefined, siteId); + + return { + sent: false, + }; }; // Get if the opposite action is not synced. const oppositeAction = approve ? 'disapprove' : 'approve'; - return this.dataOffline.getEntry(dataId, entryId, oppositeAction, siteId).then(() => { - // Found. Just delete the action. - return this.dataOffline.deleteEntry(dataId, entryId, oppositeAction, siteId); - }).catch(() => { + const found = await this.deleteEntryOfflineAction(dataId, entryId, oppositeAction, siteId); + if (found) { + // Offline action has been found and deleted. Stop here. + return; + } - if (!this.appProvider.isOnline()) { - // App is offline, store the action. - return storeOffline(); + if (!this.appProvider.isOnline()) { + // App is offline, store the action. + return storeOffline(); + } + + try { + await this.approveEntryOnline(entryId, approve, siteId); + + return { + sent: true, + }; + } catch (error) { + if (this.utils.isWebServiceError(error)) { + // The WebService has thrown an error, this means that responses cannot be submitted. + throw error; } - return this.approveEntryOnline(entryId, approve, siteId).then(() => { - return { - sent: true, - }; - }).catch((error) => { - if (this.utils.isWebServiceError(error)) { - // The WebService has thrown an error, this means that responses cannot be submitted. - return Promise.reject(error); - } - - // Couldn't connect to server, store in offline. - return storeOffline(); - }); - }); + // Couldn't connect to server, store in offline. + return storeOffline(); + } } /** @@ -317,60 +304,45 @@ export class AddonModDataProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the action is done. */ - deleteEntry(dataId: number, entryId: number, courseId: number, siteId?: string): Promise { + async deleteEntry(dataId: number, entryId: number, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); // Convenience function to store a data to be synchronized later. - const storeOffline = (): Promise => { - return this.dataOffline.saveEntry(dataId, entryId, 'delete', courseId, undefined, undefined, undefined, siteId) - .then(() => { - return { - sent: false, - }; - }); + const storeOffline = async (): Promise => { + await this.dataOffline.saveEntry(dataId, entryId, 'delete', courseId, undefined, undefined, undefined, siteId); + + return { + sent: false, + }; }; - let justAdded = false; - // Check if the opposite action is not synced and just delete it. - return this.dataOffline.getEntryActions(dataId, entryId, siteId).then((entries) => { - if (entries && entries.length) { - // Found. Delete other actions first. - const proms = entries.map((entry) => { - if (entry.action == 'add') { - justAdded = true; - } + const addedOffline = await this.deleteEntryOfflineAction(dataId, entryId, 'add', siteId); + if (addedOffline) { + // Offline add action found and deleted. Stop here. + return; + } - return this.dataOffline.deleteEntry(dataId, entryId, entry.action, siteId); - }); + if (!this.appProvider.isOnline()) { + // App is offline, store the action. + return storeOffline(); + } - return Promise.all(proms); - } - }).then(() => { - if (justAdded) { - // The field was added offline, delete and stop. - return; + try { + await this.deleteEntryOnline(entryId, siteId); + + return { + sent: true, + }; + } catch (error) { + if (this.utils.isWebServiceError(error)) { + // The WebService has thrown an error, this means that responses cannot be submitted. + throw error; } - if (!this.appProvider.isOnline()) { - // App is offline, store the action. - return storeOffline(); - } - - return this.deleteEntryOnline(entryId, siteId).then(() => { - return { - sent: true, - }; - }).catch((error) => { - if (this.utils.isWebServiceError(error)) { - // The WebService has thrown an error, this means that responses cannot be submitted. - return Promise.reject(error); - } - - // Couldn't connect to server, store in offline. - return storeOffline(); - }); - }); + // Couldn't connect to server, store in offline. + return storeOffline(); + } } /** @@ -390,6 +362,29 @@ export class AddonModDataProvider { }); } + /** + * Delete entry offline action. + * + * @param dataId Database ID. + * @param entryId Entry ID. + * @param action Action name to delete. + * @param siteId Site ID. + * @return Resolved with true if the action has been found and deleted. + */ + protected async deleteEntryOfflineAction(dataId: number, entryId: number, action: string, siteId: string): Promise { + // Get other not not synced actions. + try { + await this.dataOffline.getEntry(dataId, entryId, action, siteId); + + await this.dataOffline.deleteEntry(dataId, entryId, action, siteId); + + return true; + } catch (error) { + // Not found. + return false; + } + } + /** * Updates an existing entry. * @@ -402,64 +397,50 @@ export class AddonModDataProvider { * @param forceOffline Force editing entry in offline. * @return Promise resolved when the action is done. */ - editEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], fields: any, siteId?: string, - forceOffline: boolean = false): Promise { + async editEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], fields: any, + siteId?: string, forceOffline: boolean = false): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); // Convenience function to store a data to be synchronized later. - const storeOffline = (): Promise => { - return this.dataOffline.saveEntry(dataId, entryId, 'edit', courseId, undefined, contents, undefined, siteId) - .then(() => { - return { - updated: true, - sent: false, - }; - }); + const storeOffline = async (): Promise => { + await this.dataOffline.saveEntry(dataId, entryId, 'edit', courseId, undefined, contents, undefined, siteId); + + return { + updated: true, + sent: false, + }; }; if (!this.appProvider.isOnline() || forceOffline) { const notifications = this.checkFields(fields, contents); if (notifications) { - return Promise.resolve({ - fieldnotifications: notifications - }); + return { fieldnotifications: notifications }; } } - // Get other not not synced actions. - return this.dataOffline.getEntryActions(dataId, entryId, siteId).then((entries) => { - if (entries && entries.length) { - // Found. Delete add and edit actions first. - const proms = []; - entries.forEach((entry) => { - if (entry.action == 'edit') { - proms.push(this.dataOffline.deleteEntry(dataId, entryId, entry.action, siteId)); - } - }); + // Remove unnecessary not synced actions. + await this.deleteEntryOfflineAction(dataId, entryId, 'edit', siteId); - return Promise.all(proms); - } - }).then(() => { - if (!this.appProvider.isOnline() || forceOffline) { - // App is offline, store the action. - return storeOffline(); + if (!this.appProvider.isOnline() || forceOffline) { + // App is offline, store the action. + return storeOffline(); + } + + try { + const result = await this.editEntryOnline(entryId, contents, siteId); + result.sent = true; + + return result; + } catch (error) { + if (this.utils.isWebServiceError(error)) { + // The WebService has thrown an error, this means that responses cannot be submitted. + throw error; } - return this.editEntryOnline(entryId, contents, siteId).then((result) => { - result.sent = true; - - return result; - }).catch((error) => { - if (this.utils.isWebServiceError(error)) { - // The WebService has thrown an error, this means that responses cannot be submitted. - return Promise.reject(error); - } - - // Couldn't connect to server, store in offline. - return storeOffline(); - }); - }); - } + // Couldn't connect to server, store in offline. + return storeOffline(); + } +} /** * Updates an existing entry. It does not cache calls. It will fail if offline or cannot connect.