From 88269125b6567e8e258bc6b77c9f3ff7c1dc6dd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 9 May 2018 11:47:40 +0200 Subject: [PATCH] MOBILE-2338 data: Implement link handlers --- .../data/classes/field-plugin-component.ts | 2 - .../mod/data/components/action/action.html | 10 +- .../components/field-plugin/field-plugin.ts | 9 -- src/addon/mod/data/data.module.ts | 16 ++- .../mod/data/fields/url/component/url.html | 2 +- src/addon/mod/data/lang/en.json | 40 +++++- .../data/providers/approve-link-handler.ts | 122 ++++++++++++++++++ src/addon/mod/data/providers/data.ts | 119 ++++++++++++++++- .../mod/data/providers/delete-link-handler.ts | 121 +++++++++++++++++ .../mod/data/providers/edit-link-handler.ts | 93 +++++++++++++ src/addon/mod/data/providers/offline.ts | 48 ++++++- .../mod/data/providers/show-link-handler.ts | 105 +++++++++++++++ 12 files changed, 660 insertions(+), 27 deletions(-) create mode 100644 src/addon/mod/data/providers/approve-link-handler.ts create mode 100644 src/addon/mod/data/providers/delete-link-handler.ts create mode 100644 src/addon/mod/data/providers/edit-link-handler.ts create mode 100644 src/addon/mod/data/providers/show-link-handler.ts diff --git a/src/addon/mod/data/classes/field-plugin-component.ts b/src/addon/mod/data/classes/field-plugin-component.ts index dcd853b82..bcff4da64 100644 --- a/src/addon/mod/data/classes/field-plugin-component.ts +++ b/src/addon/mod/data/classes/field-plugin-component.ts @@ -23,6 +23,4 @@ export class AddonModDataFieldPluginComponent { @Input() database?: any; // Database object. @Input() error?: string; // Error when editing. @Input() viewAction: string; // Action to perform. - - constructor() { } } diff --git a/src/addon/mod/data/components/action/action.html b/src/addon/mod/data/components/action/action.html index 9588699a2..53f5096d7 100644 --- a/src/addon/mod/data/components/action/action.html +++ b/src/addon/mod/data/components/action/action.html @@ -1,12 +1,12 @@ - + - + - + @@ -14,11 +14,11 @@ - + - + diff --git a/src/addon/mod/data/components/field-plugin/field-plugin.ts b/src/addon/mod/data/components/field-plugin/field-plugin.ts index 8fcf975f0..0dbf7c501 100644 --- a/src/addon/mod/data/components/field-plugin/field-plugin.ts +++ b/src/addon/mod/data/components/field-plugin/field-plugin.ts @@ -71,13 +71,4 @@ export class AddonModDataFieldPluginComponent implements OnInit { } }); } - - /** - * Invalidate the plugin data. - * - * @return {Promise} Promise resolved when done. - */ - invalidate(): Promise { - return Promise.resolve(this.dynamicComponent && this.dynamicComponent.callComponentFunction('invalidate', [])); - } } diff --git a/src/addon/mod/data/data.module.ts b/src/addon/mod/data/data.module.ts index 12f6eb73b..6231d8e79 100644 --- a/src/addon/mod/data/data.module.ts +++ b/src/addon/mod/data/data.module.ts @@ -21,6 +21,10 @@ import { AddonModDataComponentsModule } from './components/components.module'; import { AddonModDataModuleHandler } from './providers/module-handler'; import { AddonModDataProvider } from './providers/data'; import { AddonModDataLinkHandler } from './providers/link-handler'; +import { AddonModDataApproveLinkHandler } from './providers/approve-link-handler'; +import { AddonModDataDeleteLinkHandler } from './providers/delete-link-handler'; +import { AddonModDataShowLinkHandler } from './providers/show-link-handler'; +import { AddonModDataEditLinkHandler } from './providers/edit-link-handler'; import { AddonModDataHelperProvider } from './providers/helper'; import { AddonModDataPrefetchHandler } from './providers/prefetch-handler'; import { AddonModDataSyncProvider } from './providers/sync'; @@ -43,6 +47,10 @@ import { AddonModDataFieldModule } from './fields/field.module'; AddonModDataPrefetchHandler, AddonModDataHelperProvider, AddonModDataLinkHandler, + AddonModDataApproveLinkHandler, + AddonModDataDeleteLinkHandler, + AddonModDataShowLinkHandler, + AddonModDataEditLinkHandler, AddonModDataSyncCronHandler, AddonModDataSyncProvider, AddonModDataOfflineProvider, @@ -54,10 +62,16 @@ export class AddonModDataModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModDataModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModDataPrefetchHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModDataLinkHandler, - cronDelegate: CoreCronDelegate, syncHandler: AddonModDataSyncCronHandler) { + cronDelegate: CoreCronDelegate, syncHandler: AddonModDataSyncCronHandler, + approveLinkHandler: AddonModDataApproveLinkHandler, deleteLinkHandler: AddonModDataDeleteLinkHandler, + showLinkHandler: AddonModDataShowLinkHandler, editLinkHandler: AddonModDataEditLinkHandler) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); contentLinksDelegate.registerHandler(linkHandler); + contentLinksDelegate.registerHandler(approveLinkHandler); + contentLinksDelegate.registerHandler(deleteLinkHandler); + contentLinksDelegate.registerHandler(showLinkHandler); + contentLinksDelegate.registerHandler(editLinkHandler); cronDelegate.register(syncHandler); } } diff --git a/src/addon/mod/data/fields/url/component/url.html b/src/addon/mod/data/fields/url/component/url.html index ddd31d55f..9212b3aae 100644 --- a/src/addon/mod/data/fields/url/component/url.html +++ b/src/addon/mod/data/fields/url/component/url.html @@ -4,4 +4,4 @@ -{{field.name}} \ No newline at end of file +{{field.name}} \ No newline at end of file diff --git a/src/addon/mod/data/lang/en.json b/src/addon/mod/data/lang/en.json index 0e0dcd235..fdd3f402a 100644 --- a/src/addon/mod/data/lang/en.json +++ b/src/addon/mod/data/lang/en.json @@ -1,3 +1,41 @@ { - + "addentries": "Add entries", + "advancedsearch": "Advanced search", + "alttext": "Alternative text", + "approve": "Approve", + "approved": "Approved", + "ascending": "Ascending", + "authorfirstname": "Author first name", + "authorlastname": "Author surname", + "confirmdeleterecord": "Are you sure you want to delete this entry?", + "descending": "Descending", + "disapprove": "Undo approval", + "emptyaddform": "You did not fill out any fields!", + "entrieslefttoadd": "You must add {{$a.entriesleft}} more entry/entries in order to complete this activity", + "entrieslefttoaddtoview": "You must add {{$a.entrieslefttoview}} more entry/entries before you can view other participants' entries.", + "errorapproving": "Error approving or unapproving entry.", + "errordeleting": "Error deleting entry.", + "errormustsupplyvalue": "You must supply a value here.", + "expired": "Sorry, this activity closed on {{$a}} and is no longer available", + "fields": "Fields", + "latlongboth": "Both latitude and longitude are required.", + "menuchoose": "Choose...", + "more": "More", + "nomatch": "No matching entries found!", + "norecords": "No entries in database", + "notapproved": "Entry is not approved yet.", + "notopenyet": "Sorry, this activity is not available until {{$a}}", + "numrecords": "{{$a}} entries", + "other": "Other", + "recordapproved": "Entry approved", + "recorddeleted": "Entry deleted", + "recorddisapproved": "Entry unapproved", + "resetsettings": "Reset filters", + "search": "Search", + "single": "View single", + "selectedrequired": "All selected required", + "single": "View single", + "timeadded": "Time added", + "timemodified": "Time modified", + "usedate": "Include in search." } \ No newline at end of file diff --git a/src/addon/mod/data/providers/approve-link-handler.ts b/src/addon/mod/data/providers/approve-link-handler.ts new file mode 100644 index 000000000..bd3c9c71a --- /dev/null +++ b/src/addon/mod/data/providers/approve-link-handler.ts @@ -0,0 +1,122 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; +import { AddonModDataProvider } from './data'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreEventsProvider } from '@providers/events'; + +/** + * Content links handler for database approve/disapprove entry. + * Match mod/data/view.php?d=6&approve=5 with a valid data id and entryid. + */ +@Injectable() +export class AddonModDataApproveLinkHandler extends CoreContentLinksHandlerBase { + name = 'AddonModDataApproveLinkHandler'; + featureName = 'CoreCourseModuleDelegate_AddonModData'; + pattern = /\/mod\/data\/view\.php.*([\?\&](d|approve|disapprove)=\d+)/; + + constructor(private dataProvider: AddonModDataProvider, private courseProvider: CoreCourseProvider, + private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider) { + super(); + } + + /** + * Convenience function to help get courseId. + * + * @param {number} dataId Database Id. + * @param {string} siteId Site Id, if not set, current site will be used. + * @param {number} courseId Course Id if already set. + * @return {Promise} Resolved with course Id when done. + */ + protected getActivityCourseIdIfNotSet(dataId: number, siteId: string, courseId: number): Promise { + if (courseId) { + return Promise.resolve(courseId); + } + + return this.courseProvider.getModuleBasicInfoByInstance(dataId, 'data', siteId).then((module) => { + return module.course; + }); + } + + /** + * Get the list of actions for a link (url). + * + * @param {string[]} siteIds List of sites the URL belongs to. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { + return [{ + action: (siteId, navCtrl?): void => { + const modal = this.domUtils.showModalLoading(), + dataId = parseInt(params.d, 10), + entryId = parseInt(params.approve, 10) || parseInt(params.disapprove, 10), + approve = parseInt(params.approve, 10) ? true : false; + + this.getActivityCourseIdIfNotSet(dataId, siteId, courseId).then((cId) => { + courseId = cId; + + // Approve/disapprove entry. + return this.dataProvider.approveEntry(dataId, entryId, approve, courseId, siteId).catch((message) => { + modal.dismiss(); + this.domUtils.showErrorModalDefault(message, 'addon.mod_data.errorapproving', true); + + return Promise.reject(null); + }); + }).then(() => { + const promises = []; + promises.push(this.dataProvider.invalidateEntryData(dataId, entryId, siteId)); + promises.push(this.dataProvider.invalidateEntriesData(dataId, siteId)); + + return Promise.all(promises); + }).then(() => { + this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId: dataId, entryId: entryId}, siteId); + + modal.dismiss(); + this.domUtils.showToast(approve ? 'addon.mod_data.recordapproved' : 'addon.mod_data.recorddisapproved', true, + 3000); + }).finally(() => { + // Just in case. In fact we need to dismiss the modal before showing a toast or error message. + modal.dismiss(); + }); + } + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param {string} siteId The site ID. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + if (typeof params.d == 'undefined' || (typeof params.approve == 'undefined' && typeof params.disapprove == 'undefined')) { + // Required fields not defined. Cannot treat the URL. + return false; + } + + return this.dataProvider.isPluginEnabled(siteId); + } +} diff --git a/src/addon/mod/data/providers/data.ts b/src/addon/mod/data/providers/data.ts index 5ffa8fdaa..b4beec32b 100644 --- a/src/addon/mod/data/providers/data.ts +++ b/src/addon/mod/data/providers/data.ts @@ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreFilepoolProvider } from '@providers/filepool'; import { AddonModDataOfflineProvider } from './offline'; +import { CoreAppProvider } from '@providers/app'; /** * Service that provides some features for databases. @@ -32,7 +33,8 @@ export class AddonModDataProvider { protected logger; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, - private filepoolProvider: CoreFilepoolProvider, private dataOffline: AddonModDataOfflineProvider) { + private filepoolProvider: CoreFilepoolProvider, private dataOffline: AddonModDataOfflineProvider, + private appProvider: CoreAppProvider) { this.logger = logger.getInstance('AddonModDataProvider'); } @@ -60,6 +62,51 @@ export class AddonModDataProvider { }); } + /** + * Approves or unapproves an entry. + * + * @param {number} dataId Database ID. + * @param {number} entryId Entry ID. + * @param {boolean} approve Whether to approve (true) or unapprove the entry. + * @param {number} courseId Course ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the action is done. + */ + 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 action = approve ? 'approve' : 'disapprove'; + + return this.dataOffline.saveEntry(dataId, entryId, action, courseId, null, null, null, siteId); + }; + + // 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(() => { + + if (!this.appProvider.isOnline()) { + // App is offline, store the action. + return storeOffline(); + } + + return this.approveEntryOnline(entryId, approve, siteId).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(); + }); + }); + } + /** * Approves or unapproves an entry. It does not cache calls. It will fail if offline or cannot connect. * @@ -79,6 +126,62 @@ export class AddonModDataProvider { }); } + /** + * Deletes an entry. + * + * @param {number} dataId Database ID. + * @param {number} entryId Entry ID. + * @param {number} courseId Course ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the action is done. + */ + 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, null, null, null, siteId); + }; + + 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; + } + + return this.dataOffline.deleteEntry(dataId, entryId, entry.action, siteId); + }); + + return Promise.all(proms); + } + }).then(() => { + if (justAdded) { + // The field was added offline, delete and stop. + return; + } + + if (!this.appProvider.isOnline()) { + // App is offline, store the action. + return storeOffline(); + } + + return this.deleteEntryOnline(entryId, siteId).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(); + }); + }); + } + /** * Deletes an entry. It does not cache calls. It will fail if offline or cannot connect. * @@ -494,6 +597,20 @@ export class AddonModDataProvider { }); } + /** + * Invalidates database entry data. + * + * @param {number} dataId Data ID for caching purposes. + * @param {number} entryId Entry ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateEntryData(dataId: number, entryId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getEntryCacheKey(dataId, entryId)); + }); + } + /** * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the database WS are available. * diff --git a/src/addon/mod/data/providers/delete-link-handler.ts b/src/addon/mod/data/providers/delete-link-handler.ts new file mode 100644 index 000000000..43af7fb8a --- /dev/null +++ b/src/addon/mod/data/providers/delete-link-handler.ts @@ -0,0 +1,121 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; +import { AddonModDataProvider } from './data'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreEventsProvider } from '@providers/events'; + +/** + * Content links handler for database delete entry. + * Match mod/data/view.php?d=6&delete=5 with a valid data id and entryid. + */ +@Injectable() +export class AddonModDataDeleteLinkHandler extends CoreContentLinksHandlerBase { + name = 'AddonModDataDeleteLinkHandler'; + featureName = 'CoreCourseModuleDelegate_AddonModData'; + pattern = /\/mod\/data\/view\.php.*([\?\&](d|delete)=\d+)/; + + constructor(private dataProvider: AddonModDataProvider, private courseProvider: CoreCourseProvider, + private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider) { + super(); + } + + /** + * Convenience function to help get courseId. + * + * @param {number} dataId Database Id. + * @param {string} siteId Site Id, if not set, current site will be used. + * @param {number} courseId Course Id if already set. + * @return {Promise} Resolved with course Id when done. + */ + protected getActivityCourseIdIfNotSet(dataId: number, siteId: string, courseId: number): Promise { + if (courseId) { + return Promise.resolve(courseId); + } + + return this.courseProvider.getModuleBasicInfoByInstance(dataId, 'data', siteId).then((module) => { + return module.course; + }); + } + + /** + * Get the list of actions for a link (url). + * + * @param {string[]} siteIds List of sites the URL belongs to. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { + return [{ + action: (siteId, navCtrl?): void => { + const modal = this.domUtils.showModalLoading(), + dataId = parseInt(params.d, 10), + entryId = parseInt(params.delete, 10); + + this.getActivityCourseIdIfNotSet(dataId, siteId, courseId).then((cId) => { + courseId = cId; + + // Delete entry. + return this.dataProvider.deleteEntry(dataId, entryId, courseId, siteId).catch((message) => { + modal.dismiss(); + this.domUtils.showErrorModalDefault(message, 'addon.mod_data.errordeleting', true); + + return Promise.reject(null); + }); + }).then(() => { + const promises = []; + promises.push(this.dataProvider.invalidateEntryData(dataId, entryId, siteId)); + promises.push(this.dataProvider.invalidateEntriesData(dataId, siteId)); + + return Promise.all(promises); + }).then(() => { + this.eventsProvider.trigger(AddonModDataProvider.ENTRY_CHANGED, {dataId: dataId, entryId: entryId, + deleted: true}, siteId); + + modal.dismiss(); + this.domUtils.showToast('addon.mod_data.recorddeleted', true, 3000); + }).finally(() => { + // Just in case. In fact we need to dismiss the modal before showing a toast or error message. + modal.dismiss(); + }); + } + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param {string} siteId The site ID. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + if (typeof params.d == 'undefined' || typeof params.delete == 'undefined') { + // Required fields not defined. Cannot treat the URL. + return false; + } + + return this.dataProvider.isPluginEnabled(siteId); + } +} diff --git a/src/addon/mod/data/providers/edit-link-handler.ts b/src/addon/mod/data/providers/edit-link-handler.ts new file mode 100644 index 000000000..448035aeb --- /dev/null +++ b/src/addon/mod/data/providers/edit-link-handler.ts @@ -0,0 +1,93 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { AddonModDataProvider } from './data'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; + +/** + * Content links handler for database add or edit entry. + * Match mod/data/edit.php?d=6&rid=6 with a valid data and optional record id. + */ +@Injectable() +export class AddonModDataEditLinkHandler extends CoreContentLinksHandlerBase { + name = 'AddonModDataEditLinkHandler'; + featureName = 'CoreCourseModuleDelegate_AddonModData'; + pattern = /\/mod\/data\/edit\.php.*([\?\&](d|rid)=\d+)/; + + constructor(private linkHelper: CoreContentLinksHelperProvider, private dataProvider: AddonModDataProvider, + private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider) { + super(); + } + + /** + * Get the list of actions for a link (url). + * + * @param {string[]} siteIds List of sites the URL belongs to. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { + return [{ + action: (siteId, navCtrl?): void => { + const modal = this.domUtils.showModalLoading(), + dataId = parseInt(params.d, 10), + rId = parseInt(params.rid, 10) || false; + + this.courseProvider.getModuleBasicInfoByInstance(dataId, 'data', siteId).then((module) => { + const stateParams = { + moduleId: module.id, + module: module, + courseId: module.course + }; + + if (rId) { + stateParams['entryId'] = rId; + } + + return this.linkHelper.goInSite(navCtrl, 'AddonModDataEditPage', stateParams, siteId); + }).finally(() => { + // Just in case. In fact we need to dismiss the modal before showing a toast or error message. + modal.dismiss(); + }); + } + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param {string} siteId The site ID. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + if (typeof params.d == 'undefined') { + // Id not defined. Cannot treat the URL. + return false; + } + + return this.dataProvider.isPluginEnabled(siteId); + } +} diff --git a/src/addon/mod/data/providers/offline.ts b/src/addon/mod/data/providers/offline.ts index 03cff2081..c30316b1a 100644 --- a/src/addon/mod/data/providers/offline.ts +++ b/src/addon/mod/data/providers/offline.ts @@ -27,10 +27,10 @@ export class AddonModDataOfflineProvider { protected logger; // Variables for database. - protected SURVEY_TABLE = 'addon_mod_data_entry'; + protected DATA_ENTRY_TABLE = 'addon_mod_data_entry'; protected tablesSchema = [ { - name: this.SURVEY_TABLE, + name: this.DATA_ENTRY_TABLE, columns: [ { name: 'dataid', @@ -102,7 +102,7 @@ export class AddonModDataOfflineProvider { */ deleteEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(this.SURVEY_TABLE, {dataid: dataId, entryid: entryId, action: action}); + return site.getDb().deleteRecords(this.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId, action: action}); }); } @@ -114,7 +114,7 @@ export class AddonModDataOfflineProvider { */ getAllEntries(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getAllRecords(this.SURVEY_TABLE); + return site.getDb().getAllRecords(this.DATA_ENTRY_TABLE); }); } @@ -127,7 +127,7 @@ export class AddonModDataOfflineProvider { */ getDatabaseEntries(dataId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.SURVEY_TABLE, {dataid: dataId}); + return site.getDb().getRecords(this.DATA_ENTRY_TABLE, {dataid: dataId}); }); } @@ -142,7 +142,7 @@ export class AddonModDataOfflineProvider { */ getEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.SURVEY_TABLE, {dataid: dataId, entryid: entryId, action: action}); + return site.getDb().getRecord(this.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId, action: action}); }); } @@ -156,7 +156,7 @@ export class AddonModDataOfflineProvider { */ getEntryActions(dataId: number, entryId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecords(this.SURVEY_TABLE, {dataid: dataId, entryid: entryId}); + return site.getDb().getRecords(this.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId}); }); } @@ -207,4 +207,38 @@ export class AddonModDataOfflineProvider { return this.textUtils.concatenatePaths(folderPath, entryId + '_' + fieldId); }); } + + /** + * Save an entry data to be sent later. + * + * @param {number} dataId Database ID. + * @param {number} entryId Database entry Id. If action is add entryId should be 0 and -timemodified will be used. + * @param {string} action Action to be done to the entry: [add, edit, delete, approve, disapprove] + * @param {number} courseId Course ID of the database. + * @param {number} [groupId] Group ID. Only provided when adding. + * @param {any[]} [fields] Array of field data of the entry if needed. + * @param {number} [timemodified] The time the entry was modified. If not defined, current time. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved if stored, rejected if failure. + */ + saveEntry(dataId: number, entryId: number, action: string, courseId: number, groupId?: number, fields?: any[], + timemodified?: number, siteId?: string): Promise { + + return this.sitesProvider.getSite(siteId).then((site) => { + timemodified = timemodified || new Date().getTime(); + entryId = typeof entryId == 'undefined' || entryId === null ? -timemodified : entryId; + const entry = { + dataid: dataId, + courseid: courseId, + groupid: groupId, + action: action, + entryid: entryId, + fields: fields, + timemodified: timemodified + }; + + return site.getDb().insertRecord(this.DATA_ENTRY_TABLE, entry); + }); + } + } diff --git a/src/addon/mod/data/providers/show-link-handler.ts b/src/addon/mod/data/providers/show-link-handler.ts new file mode 100644 index 000000000..702a03345 --- /dev/null +++ b/src/addon/mod/data/providers/show-link-handler.ts @@ -0,0 +1,105 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { AddonModDataProvider } from './data'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; + +/** + * Content links handler for database show entry. + * Match mod/data/view.php?d=6&rid=5 with a valid data id and entryid. + */ +@Injectable() +export class AddonModDataShowLinkHandler extends CoreContentLinksHandlerBase { + name = 'AddonModDataShowLinkHandler'; + featureName = 'CoreCourseModuleDelegate_AddonModData'; + pattern = /\/mod\/data\/view\.php.*([\?\&](d|rid|page|group|mode)=\d+)/; + + constructor(private linkHelper: CoreContentLinksHelperProvider, private dataProvider: AddonModDataProvider, + private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider) { + super(); + } + + /** + * Get the list of actions for a link (url). + * + * @param {string[]} siteIds List of sites the URL belongs to. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { + return [{ + action: (siteId, navCtrl?): void => { + const modal = this.domUtils.showModalLoading(), + dataId = parseInt(params.d, 10), + rId = parseInt(params.rid, 10) || false, + group = parseInt(params.group, 10) || false, + page = parseInt(params.page, 10) || false; + + this.courseProvider.getModuleBasicInfoByInstance(dataId, 'data', siteId).then((module) => { + const stateParams = { + moduleId: module.id, + module: module, + courseId: module.course + }; + + if (group) { + stateParams['group'] = group; + } + + if (params.mode && params.mode == 'single') { + stateParams['page'] = page || 1; + } else if (rId) { + stateParams['entryId'] = rId; + } + + return this.linkHelper.goInSite(navCtrl, 'AddonModDataEntryPage', stateParams, siteId); + }).finally(() => { + // Just in case. In fact we need to dismiss the modal before showing a toast or error message. + modal.dismiss(); + }); + } + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param {string} siteId The site ID. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + if (typeof params.d == 'undefined') { + // Id not defined. Cannot treat the URL. + return false; + } + + if ((!params.mode || params.mode != 'single') && typeof params.rid == 'undefined') { + return false; + } + + return this.dataProvider.isPluginEnabled(siteId); + } +}