MOBILE-2338 data: Implement link handlers

main
Pau Ferrer Ocaña 2018-05-09 11:47:40 +02:00
parent 44e8acbb5e
commit 88269125b6
12 changed files with 660 additions and 27 deletions

View File

@ -23,6 +23,4 @@ export class AddonModDataFieldPluginComponent {
@Input() database?: any; // Database object. @Input() database?: any; // Database object.
@Input() error?: string; // Error when editing. @Input() error?: string; // Error when editing.
@Input() viewAction: string; // Action to perform. @Input() viewAction: string; // Action to perform.
constructor() { }
} }

View File

@ -1,12 +1,12 @@
<a *ngIf="action == 'more'" ion-button icon-only clear [href]="url" core-link capture-link="true" [title]="'addon.mod_data.more' | translate"> <a *ngIf="action == 'more'" ion-button icon-only clear [href]="url" core-link capture="true" [title]="'addon.mod_data.more' | translate">
<ion-icon name="search"></ion-icon> <ion-icon name="search"></ion-icon>
</a> </a>
<a *ngIf="action == 'edit'" ion-button icon-only clear [href]="url" core-link capture-link="true" [title]="'core.edit' | translate"> <a *ngIf="action == 'edit'" ion-button icon-only clear [href]="url" core-link capture="true" [title]="'core.edit' | translate">
<ion-icon name="cog"></ion-icon> <ion-icon name="cog"></ion-icon>
</a> </a>
<a *ngIf="action == 'delete' && !entry.deleted" ion-button icon-only clear [href]="url" core-link capture-link="true" [title]="'core.delete' | translate"> <a *ngIf="action == 'delete' && !entry.deleted" ion-button icon-only clear [href]="url" core-link capture="true" [title]="'core.delete' | translate">
<ion-icon name="trash"></ion-icon> <ion-icon name="trash"></ion-icon>
</a> </a>
@ -14,11 +14,11 @@
<ion-icon name="undo"></ion-icon> <ion-icon name="undo"></ion-icon>
</a> </a>
<a *ngIf="action == 'approve'" ion-button icon-only clear [href]="url" core-link capture-link="true" [title]="'addon.mod_data.approve' | translate"> <a *ngIf="action == 'approve'" ion-button icon-only clear [href]="url" core-link capture="true" [title]="'addon.mod_data.approve' | translate">
<ion-icon name="thumbs-up"></ion-icon> <ion-icon name="thumbs-up"></ion-icon>
</a> </a>
<a *ngIf="action == 'disapprove'" ion-button icon-only clear [href]="url" core-link capture-link="true" [title]="'addon.mod_data.disapprove' | translate"> <a *ngIf="action == 'disapprove'" ion-button icon-only clear [href]="url" core-link capture="true" [title]="'addon.mod_data.disapprove' | translate">
<ion-icon name="thumbs-down"></ion-icon> <ion-icon name="thumbs-down"></ion-icon>
</a> </a>

View File

@ -71,13 +71,4 @@ export class AddonModDataFieldPluginComponent implements OnInit {
} }
}); });
} }
/**
* Invalidate the plugin data.
*
* @return {Promise<any>} Promise resolved when done.
*/
invalidate(): Promise<any> {
return Promise.resolve(this.dynamicComponent && this.dynamicComponent.callComponentFunction('invalidate', []));
}
} }

View File

@ -21,6 +21,10 @@ import { AddonModDataComponentsModule } from './components/components.module';
import { AddonModDataModuleHandler } from './providers/module-handler'; import { AddonModDataModuleHandler } from './providers/module-handler';
import { AddonModDataProvider } from './providers/data'; import { AddonModDataProvider } from './providers/data';
import { AddonModDataLinkHandler } from './providers/link-handler'; 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 { AddonModDataHelperProvider } from './providers/helper';
import { AddonModDataPrefetchHandler } from './providers/prefetch-handler'; import { AddonModDataPrefetchHandler } from './providers/prefetch-handler';
import { AddonModDataSyncProvider } from './providers/sync'; import { AddonModDataSyncProvider } from './providers/sync';
@ -43,6 +47,10 @@ import { AddonModDataFieldModule } from './fields/field.module';
AddonModDataPrefetchHandler, AddonModDataPrefetchHandler,
AddonModDataHelperProvider, AddonModDataHelperProvider,
AddonModDataLinkHandler, AddonModDataLinkHandler,
AddonModDataApproveLinkHandler,
AddonModDataDeleteLinkHandler,
AddonModDataShowLinkHandler,
AddonModDataEditLinkHandler,
AddonModDataSyncCronHandler, AddonModDataSyncCronHandler,
AddonModDataSyncProvider, AddonModDataSyncProvider,
AddonModDataOfflineProvider, AddonModDataOfflineProvider,
@ -54,10 +62,16 @@ export class AddonModDataModule {
constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModDataModuleHandler, constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModDataModuleHandler,
prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModDataPrefetchHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModDataPrefetchHandler,
contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModDataLinkHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModDataLinkHandler,
cronDelegate: CoreCronDelegate, syncHandler: AddonModDataSyncCronHandler) { cronDelegate: CoreCronDelegate, syncHandler: AddonModDataSyncCronHandler,
approveLinkHandler: AddonModDataApproveLinkHandler, deleteLinkHandler: AddonModDataDeleteLinkHandler,
showLinkHandler: AddonModDataShowLinkHandler, editLinkHandler: AddonModDataEditLinkHandler) {
moduleDelegate.registerHandler(moduleHandler); moduleDelegate.registerHandler(moduleHandler);
prefetchDelegate.registerHandler(prefetchHandler); prefetchDelegate.registerHandler(prefetchHandler);
contentLinksDelegate.registerHandler(linkHandler); contentLinksDelegate.registerHandler(linkHandler);
contentLinksDelegate.registerHandler(approveLinkHandler);
contentLinksDelegate.registerHandler(deleteLinkHandler);
contentLinksDelegate.registerHandler(showLinkHandler);
contentLinksDelegate.registerHandler(editLinkHandler);
cronDelegate.register(syncHandler); cronDelegate.register(syncHandler);
} }
} }

View File

@ -4,4 +4,4 @@
<ion-input *ngIf="mode != 'show'" type="url" [formControlName]="'f_'+field.id" [placeholder]="field.name" [(ngModel)]="val"></ion-input> <ion-input *ngIf="mode != 'show'" type="url" [formControlName]="'f_'+field.id" [placeholder]="field.name" [(ngModel)]="val"></ion-input>
<a *ngIf="mode == 'show' && value && value.content" [href]="value.content" core-link capture-link="true">{{field.name}}</a> <a *ngIf="mode == 'show' && value && value.content" [href]="value.content" core-link capture="true">{{field.name}}</a>

View File

@ -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."
} }

View File

@ -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<number>} Resolved with course Id when done.
*/
protected getActivityCourseIdIfNotSet(dataId: number, siteId: string, courseId: number): Promise<number> {
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<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
*/
getActions(siteIds: string[], url: string, params: any, courseId?: number):
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
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<boolean>} Whether the handler is enabled for the URL and site.
*/
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
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);
}
}

View File

@ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreFilepoolProvider } from '@providers/filepool';
import { AddonModDataOfflineProvider } from './offline'; import { AddonModDataOfflineProvider } from './offline';
import { CoreAppProvider } from '@providers/app';
/** /**
* Service that provides some features for databases. * Service that provides some features for databases.
@ -32,7 +33,8 @@ export class AddonModDataProvider {
protected logger; protected logger;
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, 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'); 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<any>} Promise resolved when the action is done.
*/
approveEntry(dataId: number, entryId: number, approve: boolean, courseId: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
// Convenience function to store a data to be synchronized later.
const storeOffline = (): Promise<any> => {
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. * 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<any>} Promise resolved when the action is done.
*/
deleteEntry(dataId: number, entryId: number, courseId: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
// Convenience function to store a data to be synchronized later.
const storeOffline = (): Promise<any> => {
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. * 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<any>} Promise resolved when the data is invalidated.
*/
invalidateEntryData(dataId: number, entryId: number, siteId?: string): Promise<any> {
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. * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the database WS are available.
* *

View File

@ -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<number>} Resolved with course Id when done.
*/
protected getActivityCourseIdIfNotSet(dataId: number, siteId: string, courseId: number): Promise<number> {
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<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
*/
getActions(siteIds: string[], url: string, params: any, courseId?: number):
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
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<boolean>} Whether the handler is enabled for the URL and site.
*/
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
if (typeof params.d == 'undefined' || typeof params.delete == 'undefined') {
// Required fields not defined. Cannot treat the URL.
return false;
}
return this.dataProvider.isPluginEnabled(siteId);
}
}

View File

@ -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<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
*/
getActions(siteIds: string[], url: string, params: any, courseId?: number):
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
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<boolean>} Whether the handler is enabled for the URL and site.
*/
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
if (typeof params.d == 'undefined') {
// Id not defined. Cannot treat the URL.
return false;
}
return this.dataProvider.isPluginEnabled(siteId);
}
}

View File

@ -27,10 +27,10 @@ export class AddonModDataOfflineProvider {
protected logger; protected logger;
// Variables for database. // Variables for database.
protected SURVEY_TABLE = 'addon_mod_data_entry'; protected DATA_ENTRY_TABLE = 'addon_mod_data_entry';
protected tablesSchema = [ protected tablesSchema = [
{ {
name: this.SURVEY_TABLE, name: this.DATA_ENTRY_TABLE,
columns: [ columns: [
{ {
name: 'dataid', name: 'dataid',
@ -102,7 +102,7 @@ export class AddonModDataOfflineProvider {
*/ */
deleteEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise<any> { deleteEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { 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<any> { getAllEntries(siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { 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<any> { getDatabaseEntries(dataId: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { 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<any> { getEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { 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<any> { getEntryActions(dataId: number, entryId: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { 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); 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<any>} 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<any> {
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);
});
}
} }

View File

@ -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<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
*/
getActions(siteIds: string[], url: string, params: any, courseId?: number):
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
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<boolean>} Whether the handler is enabled for the URL and site.
*/
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
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);
}
}