forked from EVOgeek/Vmeda.Online
		
	MOBILE-3640 database: Add database activity services
This commit is contained in:
		
							parent
							
								
									992f1ca1ad
								
							
						
					
					
						commit
						2991873dfe
					
				| @ -186,7 +186,7 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid | ||||
|      * Perform the assign submission. | ||||
|      * | ||||
|      * @param assignId Assign ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @param siteId Site ID. | ||||
|      * @return Promise resolved in success. | ||||
|      */ | ||||
|     protected async performSyncAssign(assignId: number, siteId: string): Promise<AddonModAssignSyncResult> { | ||||
|  | ||||
							
								
								
									
										50
									
								
								src/addons/mod/data/lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/addons/mod/data/lang.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | ||||
| { | ||||
|     "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", | ||||
|     "edittagsnotsupported": "Sorry, editing tags is not supported by the app.", | ||||
|     "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", | ||||
|     "foundrecords": "Found records: {{$a.num}}/{{$a.max}} (<a href=\"{{$a.reseturl}}\">Reset filters</a>)", | ||||
|     "gettinglocation": "Getting location", | ||||
|     "latlongboth": "Both latitude and longitude are required.", | ||||
|     "locationpermissiondenied": "Permission to access your location has been denied.", | ||||
|     "locationnotenabled": "Location is not enabled", | ||||
|     "menuchoose": "Choose...", | ||||
|     "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.", | ||||
|     "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", | ||||
|     "searchbytagsnotsupported": "Sorry, searching by tags is not supported by the app.", | ||||
|     "selectedrequired": "All selected required", | ||||
|     "single": "View single", | ||||
|     "tagarea_data_records": "Data records", | ||||
|     "timeadded": "Time added", | ||||
|     "timemodified": "Time modified", | ||||
|     "usedate": "Include in search." | ||||
| } | ||||
							
								
								
									
										267
									
								
								src/addons/mod/data/services/data-fields-delegate.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								src/addons/mod/data/services/data-fields-delegate.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,267 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable, Type } from '@angular/core'; | ||||
| import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; | ||||
| import { AddonModDataDefaultFieldHandler } from './handlers/default-field'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModDataEntryField, | ||||
|     AddonModDataField, | ||||
|     AddonModDataSearchEntriesAdvancedFieldFormatted, | ||||
|     AddonModDataSubfieldData, | ||||
| } from './data'; | ||||
| import { CoreFormFields } from '@singletons/form'; | ||||
| import { CoreWSExternalFile } from '@services/ws'; | ||||
| import { FileEntry } from '@ionic-native/file'; | ||||
| 
 | ||||
| /** | ||||
|  * Interface that all fields handlers must implement. | ||||
|  */ | ||||
| export interface AddonModDataFieldHandler extends CoreDelegateHandler { | ||||
| 
 | ||||
|     /** | ||||
|      * Name of the type of data field the handler supports. E.g. 'checkbox'. | ||||
|      */ | ||||
|     type: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Return the Component to use to display the plugin data. | ||||
|      * It's recommended to return the class of the component, but you can also return an instance of the component. | ||||
|      * | ||||
|      * @param field The field object. | ||||
|      * @return The component to use, undefined if not found. | ||||
|      */ | ||||
|     getComponent?(plugin: AddonModDataField): Type<unknown> | undefined; | ||||
| 
 | ||||
|     /** | ||||
|      * Get field search data in the input data. | ||||
|      * | ||||
|      * @param field Defines the field to be rendered. | ||||
|      * @param inputData Data entered in the search form. | ||||
|      * @return With name and value of the data to be sent. | ||||
|      */ | ||||
|     getFieldSearchData?( | ||||
|         field: AddonModDataField, | ||||
|         inputData: CoreFormFields, | ||||
|     ): AddonModDataSearchEntriesAdvancedFieldFormatted[]; | ||||
| 
 | ||||
|     /** | ||||
|      * Get field edit data in the input data. | ||||
|      * | ||||
|      * @param field Defines the field to be rendered. | ||||
|      * @param inputData Data entered in the edit form. | ||||
|      * @return With name and value of the data to be sent. | ||||
|      */ | ||||
|     getFieldEditData?( | ||||
|         field: AddonModDataField, | ||||
|         // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||
|         inputData: CoreFormFields<any>, | ||||
|         originalFieldData: AddonModDataEntryField, | ||||
|     ): AddonModDataSubfieldData[]; | ||||
| 
 | ||||
|     /** | ||||
|      * Get field data in changed. | ||||
|      * | ||||
|      * @param field Defines the field to be rendered. | ||||
|      * @param inputData Data entered in the edit form. | ||||
|      * @param originalFieldData Original field entered data. | ||||
|      * @return If the field has changes. | ||||
|      */ | ||||
|     hasFieldDataChanged?( | ||||
|         field: AddonModDataField, | ||||
|         inputData: CoreFormFields, | ||||
|         originalFieldData: AddonModDataEntryField, | ||||
|     ): boolean; | ||||
| 
 | ||||
|     /** | ||||
|      * Get field edit files in the input data. | ||||
|      * | ||||
|      * @param field Defines the field.. | ||||
|      * @return With name and value of the data to be sent. | ||||
|      */ | ||||
|     getFieldEditFiles?( | ||||
|         field: AddonModDataField, | ||||
|         inputData: CoreFormFields, | ||||
|         originalFieldData: AddonModDataEntryField, | ||||
|     ): (CoreWSExternalFile | FileEntry)[]; | ||||
| 
 | ||||
|     /** | ||||
|      * Check and get field requeriments. | ||||
|      * | ||||
|      * @param field Defines the field to be rendered. | ||||
|      * @param inputData Data entered in the edit form. | ||||
|      * @return String with the notification or false. | ||||
|      */ | ||||
|     getFieldsNotifications?(field: AddonModDataField, inputData: AddonModDataSubfieldData[]): string | undefined; | ||||
| 
 | ||||
|     /** | ||||
|      * Override field content data with offline submission. | ||||
|      * | ||||
|      * @param originalContent Original data to be overriden. | ||||
|      * @param offlineContent Array with all the offline data to override. | ||||
|      * @param offlineFiles Array with all the offline files in the field. | ||||
|      * @return Data overriden | ||||
|      */ | ||||
|     overrideData?( | ||||
|         originalContent: AddonModDataEntryField, | ||||
|         offlineContent: CoreFormFields, | ||||
|         offlineFiles?: FileEntry[], | ||||
|     ): AddonModDataEntryField; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Delegate to register database fields handlers. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataFieldsDelegateService extends CoreDelegate<AddonModDataFieldHandler> { | ||||
| 
 | ||||
|     protected handlerNameProperty = 'type'; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected defaultHandler: AddonModDataDefaultFieldHandler, | ||||
|     ) { | ||||
|         super('AddonModDataFieldsDelegate', true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the component to use for a certain field field. | ||||
|      * | ||||
|      * @param field The field object. | ||||
|      * @return Promise resolved with the component to use, undefined if not found. | ||||
|      */ | ||||
|     getComponentForField(field: AddonModDataField): Promise<Type<unknown> | undefined> { | ||||
|         return Promise.resolve(this.executeFunctionOnEnabled(field.type, 'getComponent', [field])); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get database data in the input data to search. | ||||
|      * | ||||
|      * @param field Defines the field to be rendered. | ||||
|      * @param inputData Data entered in the search form. | ||||
|      * @return Name and data field. | ||||
|      */ | ||||
|     getFieldSearchData(field: AddonModDataField, inputData: CoreFormFields): AddonModDataSearchEntriesAdvancedFieldFormatted[] { | ||||
|         return this.executeFunctionOnEnabled(field.type, 'getFieldSearchData', [field, inputData]) || []; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get database data in the input data to add or update entry. | ||||
|      * | ||||
|      * @param field Defines the field to be rendered. | ||||
|      * @param inputData Data entered in the search form. | ||||
|      * @param originalFieldData Original field entered data. | ||||
|      * @return Name and data field. | ||||
|      */ | ||||
|     getFieldEditData( | ||||
|         field: AddonModDataField, | ||||
|         inputData: CoreFormFields, | ||||
|         originalFieldData: AddonModDataEntryField, | ||||
|     ): AddonModDataSubfieldData[] { | ||||
|         return this.executeFunctionOnEnabled(field.type, 'getFieldEditData', [field, inputData, originalFieldData]) || []; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get database data in the input files to add or update entry. | ||||
|      * | ||||
|      * @param field Defines the field to be rendered. | ||||
|      * @param inputData Data entered in the search form. | ||||
|      * @param originalFieldData Original field entered data. | ||||
|      * @return Name and data field. | ||||
|      */ | ||||
|     getFieldEditFiles( | ||||
|         field: AddonModDataField, | ||||
|         inputData: CoreFormFields, | ||||
|         originalFieldData: CoreFormFields, | ||||
|     ): (CoreWSExternalFile | FileEntry)[] { | ||||
|         return this.executeFunctionOnEnabled(field.type, 'getFieldEditFiles', [field, inputData, originalFieldData]) || []; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check and get field requeriments. | ||||
|      * | ||||
|      * @param field Defines the field to be rendered. | ||||
|      * @param inputData Data entered in the edit form. | ||||
|      * @return String with the notification or false. | ||||
|      */ | ||||
|     getFieldsNotifications(field: AddonModDataField, inputData: AddonModDataSubfieldData[]): string | undefined { | ||||
|         return this.executeFunctionOnEnabled(field.type, 'getFieldsNotifications', [field, inputData]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if field type manage files or not. | ||||
|      * | ||||
|      * @param field Defines the field to be checked. | ||||
|      * @return If the field type manages files. | ||||
|      */ | ||||
|     hasFiles(field: AddonModDataField): boolean { | ||||
|         return this.hasFunction(field.type, 'getFieldEditFiles'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if the data has changed for a certain field. | ||||
|      * | ||||
|      * @param field Defines the field to be rendered. | ||||
|      * @param inputData Data entered in the search form. | ||||
|      * @param originalFieldData Original field entered data. | ||||
|      * @return If the field has changes. | ||||
|      */ | ||||
|     hasFieldDataChanged( | ||||
|         field: AddonModDataField, | ||||
|         inputData: CoreFormFields, | ||||
|         originalFieldData: CoreFormFields, | ||||
|     ): boolean { | ||||
|         return !!this.executeFunctionOnEnabled( | ||||
|             field.type, | ||||
|             'hasFieldDataChanged', | ||||
|             [field, inputData, originalFieldData], | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a field plugin is supported. | ||||
|      * | ||||
|      * @param pluginType Type of the plugin. | ||||
|      * @return True if supported, false otherwise. | ||||
|      */ | ||||
|     isPluginSupported(pluginType: string): boolean { | ||||
|         return this.hasHandler(pluginType, true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Override field content data with offline submission. | ||||
|      * | ||||
|      * @param field Defines the field to be rendered. | ||||
|      * @param originalContent Original data to be overriden. | ||||
|      * @param offlineContent Array with all the offline data to override. | ||||
|      * @param offlineFiles Array with all the offline files in the field. | ||||
|      * @return Data overriden | ||||
|      */ | ||||
|     overrideData( | ||||
|         field: AddonModDataField, | ||||
|         originalContent: AddonModDataEntryField, | ||||
|         offlineContent: CoreFormFields, | ||||
|         offlineFiles?: FileEntry[], | ||||
|     ): AddonModDataEntryField { | ||||
|         originalContent = originalContent || {}; | ||||
| 
 | ||||
|         if (!offlineContent) { | ||||
|             return originalContent; | ||||
|         } | ||||
| 
 | ||||
|         return this.executeFunctionOnEnabled(field.type, 'overrideData', [originalContent, offlineContent, offlineFiles]) || | ||||
|             originalContent; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataFieldsDelegate = makeSingleton(AddonModDataFieldsDelegateService); | ||||
							
								
								
									
										790
									
								
								src/addons/mod/data/services/data-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										790
									
								
								src/addons/mod/data/services/data-helper.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,790 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { ContextLevel } from '@/core/constants'; | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreCourse } from '@features/course/services/course'; | ||||
| import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader'; | ||||
| import { CoreRatingOffline } from '@features/rating/services/rating-offline'; | ||||
| import { FileEntry } from '@ionic-native/file'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreFormFields } from '@singletons/form'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreWSExternalFile } from '@services/ws'; | ||||
| import { makeSingleton, Translate } from '@singletons'; | ||||
| import { CoreEvents } from '@singletons/events'; | ||||
| import { | ||||
|     AddonModDataEntry, | ||||
|     AddonModData, | ||||
|     AddonModDataProvider, | ||||
|     AddonModDataSearchEntriesOptions, | ||||
|     AddonModDataEntries, | ||||
|     AddonModDataEntryFields, | ||||
|     AddonModDataAction, | ||||
|     AddonModDataGetEntryFormatted, | ||||
|     AddonModDataData, | ||||
|     AddonModDataTemplateType, | ||||
|     AddonModDataGetDataAccessInformationWSResponse, | ||||
|     AddonModDataTemplateMode, | ||||
|     AddonModDataField, | ||||
|     AddonModDataEntryWSField, | ||||
| } from './data'; | ||||
| import { AddonModDataFieldsDelegate } from './data-fields-delegate'; | ||||
| import { AddonModDataOffline, AddonModDataOfflineAction } from './data-offline'; | ||||
| 
 | ||||
| /** | ||||
|  * Service that provides helper functions for datas. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataHelperProvider { | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the record with the offline actions applied. | ||||
|      * | ||||
|      * @param record Entry to modify. | ||||
|      * @param offlineActions Offline data with the actions done. | ||||
|      * @param fields Entry defined fields indexed by fieldid. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async applyOfflineActions( | ||||
|         record: AddonModDataEntry, | ||||
|         offlineActions: AddonModDataOfflineAction[], | ||||
|         fields: AddonModDataField[], | ||||
|     ): Promise<AddonModDataEntry> { | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         offlineActions.forEach((action) => { | ||||
|             record.timemodified = action.timemodified; | ||||
|             record.hasOffline = true; | ||||
|             const offlineContents: Record<number, CoreFormFields> = {}; | ||||
| 
 | ||||
|             switch (action.action) { | ||||
|                 case AddonModDataAction.APPROVE: | ||||
|                     record.approved = true; | ||||
|                     break; | ||||
|                 case AddonModDataAction.DISAPPROVE: | ||||
|                     record.approved = false; | ||||
|                     break; | ||||
|                 case AddonModDataAction.DELETE: | ||||
|                     record.deleted = true; | ||||
|                     break; | ||||
|                 case AddonModDataAction.ADD: | ||||
|                 case AddonModDataAction.EDIT: | ||||
|                     record.groupid = action.groupid; | ||||
| 
 | ||||
|                     action.fields.forEach((offlineContent) => { | ||||
|                         if (typeof offlineContents[offlineContent.fieldid] == 'undefined') { | ||||
|                             offlineContents[offlineContent.fieldid] = {}; | ||||
|                         } | ||||
| 
 | ||||
|                         if (offlineContent.subfield) { | ||||
|                             offlineContents[offlineContent.fieldid][offlineContent.subfield] = | ||||
|                                 CoreTextUtils.parseJSON(offlineContent.value); | ||||
|                         } else { | ||||
|                             offlineContents[offlineContent.fieldid][''] = CoreTextUtils.parseJSON(offlineContent.value); | ||||
|                         } | ||||
|                     }); | ||||
| 
 | ||||
|                     // Override field contents.
 | ||||
|                     fields.forEach((field) => { | ||||
|                         if (AddonModDataFieldsDelegate.hasFiles(field)) { | ||||
|                             promises.push(this.getStoredFiles(record.dataid, record.id, field.id).then((offlineFiles) => { | ||||
|                                 record.contents[field.id] = AddonModDataFieldsDelegate.overrideData( | ||||
|                                     field, | ||||
|                                     record.contents[field.id], | ||||
|                                     offlineContents[field.id], | ||||
|                                     offlineFiles, | ||||
|                                 ); | ||||
|                                 record.contents[field.id].fieldid = field.id; | ||||
| 
 | ||||
|                                 return; | ||||
|                             })); | ||||
|                         } else { | ||||
|                             record.contents[field.id] = AddonModDataFieldsDelegate.overrideData( | ||||
|                                 field, | ||||
|                                 record.contents[field.id], | ||||
|                                 offlineContents[field.id], | ||||
|                             ); | ||||
|                             record.contents[field.id].fieldid = field.id; | ||||
|                         } | ||||
|                     }); | ||||
|                     break; | ||||
|                 default: | ||||
|                     break; | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
| 
 | ||||
|         return record; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Approve or disapprove a database entry. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param entryId Entry ID. | ||||
|      * @param approve True to approve, false to disapprove. | ||||
|      * @param courseId Course ID. It not defined, it will be fetched. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      */ | ||||
|     async approveOrDisapproveEntry( | ||||
|         dataId: number, | ||||
|         entryId: number, | ||||
|         approve: boolean, | ||||
|         courseId?: number, | ||||
|         siteId?: string, | ||||
|     ): Promise<void> { | ||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         const modal = await CoreDomUtils.showModalLoading('core.sending', true); | ||||
| 
 | ||||
|         try { | ||||
|             courseId = await this.getActivityCourseIdIfNotSet(dataId, courseId, siteId); | ||||
| 
 | ||||
|             try { | ||||
|                 // Approve/disapprove entry.
 | ||||
|                 await AddonModData.approveEntry(dataId, entryId, approve, courseId, siteId); | ||||
|             } catch (error) { | ||||
|                 CoreDomUtils.showErrorModalDefault(error, 'addon.mod_data.errorapproving', true); | ||||
| 
 | ||||
|                 throw error; | ||||
|             } | ||||
| 
 | ||||
|             const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|             promises.push(AddonModData.invalidateEntryData(dataId, entryId, siteId)); | ||||
|             promises.push(AddonModData.invalidateEntriesData(dataId, siteId)); | ||||
| 
 | ||||
|             await CoreUtils.ignoreErrors(Promise.all(promises)); | ||||
| 
 | ||||
|             CoreEvents.trigger(AddonModDataProvider.ENTRY_CHANGED, { dataId: dataId, entryId: entryId }, siteId); | ||||
| 
 | ||||
|             CoreDomUtils.showToast(approve ? 'addon.mod_data.recordapproved' : 'addon.mod_data.recorddisapproved', true, 3000); | ||||
|         } catch { | ||||
|             // Ignore error, it was already displayed.
 | ||||
|         } finally { | ||||
|             modal.dismiss(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Displays fields for being shown. | ||||
|      * | ||||
|      * @param template Template HMTL. | ||||
|      * @param fields Fields that defines every content in the entry. | ||||
|      * @param entry Entry. | ||||
|      * @param offset Entry offset. | ||||
|      * @param mode Mode list or show. | ||||
|      * @param actions Actions that can be performed to the record. | ||||
|      * @return Generated HTML. | ||||
|      */ | ||||
|     displayShowFields( | ||||
|         template: string, | ||||
|         fields: AddonModDataField[], | ||||
|         entry: AddonModDataEntry, | ||||
|         offset = 0, | ||||
|         mode: AddonModDataTemplateMode, | ||||
|         actions: Record<AddonModDataAction, boolean>, | ||||
|     ): string { | ||||
| 
 | ||||
|         if (!template) { | ||||
|             return ''; | ||||
|         } | ||||
| 
 | ||||
|         // Replace the fields found on template.
 | ||||
|         fields.forEach((field) => { | ||||
|             let replace = '[[' + field.name + ']]'; | ||||
|             replace = replace.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); | ||||
|             const replaceRegex = new RegExp(replace, 'gi'); | ||||
| 
 | ||||
|             // Replace field by a generic directive.
 | ||||
|             const render = '<addon-mod-data-field-plugin [field]="fields[' + field.id + ']" [value]="entries[' + entry.id + | ||||
|                     '].contents[' + field.id + ']" mode="' + mode + '" [database]="database" (gotoEntry)="gotoEntry(' + entry.id + | ||||
|                     ')"></addon-mod-data-field-plugin>'; | ||||
|             template = template.replace(replaceRegex, render); | ||||
|         }); | ||||
| 
 | ||||
|         for (const action in actions) { | ||||
|             const replaceRegex = new RegExp('##' + action + '##', 'gi'); | ||||
|             // Is enabled?
 | ||||
|             if (actions[action]) { | ||||
|                 let render = ''; | ||||
|                 if (action == AddonModDataAction.MOREURL) { | ||||
|                     // Render more url directly because it can be part of an HTML attribute.
 | ||||
|                     render = CoreSites.getCurrentSite()!.getURL() + '/mod/data/view.php?d={{database.id}}&rid=' + entry.id; | ||||
|                 } else if (action == 'approvalstatus') { | ||||
|                     render = Translate.instant('addon.mod_data.' + (entry.approved ? 'approved' : 'notapproved')); | ||||
|                 } else { | ||||
|                     render = '<addon-mod-data-action action="' + action + '" [entry]="entries[' + entry.id + ']" mode="' + mode + | ||||
|                     '" [database]="database" [module]="module" [offset]="' + offset + '" [group]="group" ></addon-mod-data-action>'; | ||||
|                 } | ||||
|                 template = template.replace(replaceRegex, render); | ||||
|             } else { | ||||
|                 template = template.replace(replaceRegex, ''); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return template; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get online and offline entries, or search entries. | ||||
|      * | ||||
|      * @param database Database object. | ||||
|      * @param fields The fields that define the contents. | ||||
|      * @param options Other options. | ||||
|      * @return Promise resolved when the database is retrieved. | ||||
|      */ | ||||
|     async fetchEntries( | ||||
|         database: AddonModDataData, | ||||
|         fields: AddonModDataField[], | ||||
|         options: AddonModDataSearchEntriesOptions = {}, | ||||
|     ): Promise<AddonModDataEntries> { | ||||
|         const site = await CoreSites.getSite(options.siteId); | ||||
|         options.groupId = options.groupId || 0; | ||||
|         options.page = options.page || 0; | ||||
| 
 | ||||
|         const offlineActions: Record<number, AddonModDataOfflineAction[]> = {}; | ||||
|         const result: AddonModDataEntries = { | ||||
|             entries: [], | ||||
|             totalcount: 0, | ||||
|             offlineEntries: [], | ||||
|         }; | ||||
|         options.siteId = site.id; | ||||
| 
 | ||||
|         const offlinePromise = AddonModDataOffline.getDatabaseEntries(database.id, site.id).then((actions) => { | ||||
|             result.hasOfflineActions = !!actions.length; | ||||
| 
 | ||||
|             actions.forEach((action) => { | ||||
|                 if (typeof offlineActions[action.entryid] == 'undefined') { | ||||
|                     offlineActions[action.entryid] = []; | ||||
|                 } | ||||
|                 offlineActions[action.entryid].push(action); | ||||
| 
 | ||||
|                 // We only display new entries in the first page when not searching.
 | ||||
|                 if (action.action == AddonModDataAction.ADD && options.page == 0 && !options.search && !options.advSearch && | ||||
|                     (!action.groupid || !options.groupId || action.groupid == options.groupId)) { | ||||
|                     result.offlineEntries!.push({ | ||||
|                         id: action.entryid, | ||||
|                         canmanageentry: true, | ||||
|                         approved: !database.approval || database.manageapproved, | ||||
|                         dataid: database.id, | ||||
|                         groupid: action.groupid, | ||||
|                         timecreated: -action.entryid, | ||||
|                         timemodified: -action.entryid, | ||||
|                         userid: site.getUserId(), | ||||
|                         fullname: site.getInfo()?.fullname, | ||||
|                         contents: {}, | ||||
|                     }); | ||||
|                 } | ||||
| 
 | ||||
|             }); | ||||
| 
 | ||||
|             // Sort offline entries by creation time.
 | ||||
|             result.offlineEntries!.sort((a, b) => b.timecreated - a.timecreated); | ||||
| 
 | ||||
|             return; | ||||
|         }); | ||||
| 
 | ||||
|         const ratingsPromise = CoreRatingOffline.hasRatings('mod_data', 'entry', ContextLevel.MODULE, database.coursemodule) | ||||
|             .then((hasRatings) => { | ||||
|                 result.hasOfflineRatings = hasRatings; | ||||
| 
 | ||||
|                 return; | ||||
|             }); | ||||
| 
 | ||||
|         let fetchPromise: Promise<void>; | ||||
|         if (options.search || options.advSearch) { | ||||
|             fetchPromise = AddonModData.searchEntries(database.id, options).then((searchResult) => { | ||||
|                 result.entries = searchResult.entries; | ||||
|                 result.totalcount = searchResult.totalcount; | ||||
|                 result.maxcount = searchResult.maxcount; | ||||
| 
 | ||||
|                 return; | ||||
|             }); | ||||
|         } else { | ||||
|             fetchPromise = AddonModData.getEntries(database.id, options).then((entriesResult) => { | ||||
|                 result.entries = entriesResult.entries; | ||||
|                 result.totalcount = entriesResult.totalcount; | ||||
| 
 | ||||
|                 return; | ||||
|             }); | ||||
|         } | ||||
|         await Promise.all([offlinePromise, ratingsPromise, fetchPromise]); | ||||
| 
 | ||||
|         // Apply offline actions to online and offline entries.
 | ||||
|         const promises: Promise<AddonModDataEntry>[] = []; | ||||
|         result.entries.forEach((entry) => { | ||||
|             promises.push(this.applyOfflineActions(entry, offlineActions[entry.id] || [], fields)); | ||||
|         }); | ||||
| 
 | ||||
|         result.offlineEntries!.forEach((entry) => { | ||||
|             promises.push(this.applyOfflineActions(entry, offlineActions[entry.id] || [], fields)); | ||||
|         }); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Fetch an online or offline entry. | ||||
|      * | ||||
|      * @param database Database. | ||||
|      * @param fields List of database fields. | ||||
|      * @param entryId Entry ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the entry. | ||||
|      */ | ||||
|     async fetchEntry( | ||||
|         database: AddonModDataData, | ||||
|         fields: AddonModDataField[], | ||||
|         entryId: number, | ||||
|         siteId?: string, | ||||
|     ): Promise<AddonModDataGetEntryFormatted> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         const offlineActions = await AddonModDataOffline.getEntryActions(database.id, entryId, site.id); | ||||
| 
 | ||||
|         let response: AddonModDataGetEntryFormatted; | ||||
|         if (entryId > 0) { | ||||
|             // Online entry.
 | ||||
|             response = await AddonModData.getEntry(database.id, entryId, { cmId: database.coursemodule, siteId: site.id }); | ||||
|         } else { | ||||
|             // Offline entry or new entry.
 | ||||
|             response = { | ||||
|                 entry: { | ||||
|                     id: entryId, | ||||
|                     userid: site.getUserId(), | ||||
|                     groupid: 0, | ||||
|                     dataid: database.id, | ||||
|                     timecreated: -entryId, | ||||
|                     timemodified: -entryId, | ||||
|                     approved: !database.approval || database.manageapproved, | ||||
|                     canmanageentry: true, | ||||
|                     fullname: site.getInfo()?.fullname, | ||||
|                     contents: {}, | ||||
|                 }, | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         await this.applyOfflineActions(response.entry, offlineActions, fields); | ||||
| 
 | ||||
|         return response; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns an object with all the actions that the user can do over the record. | ||||
|      * | ||||
|      * @param database Database activity. | ||||
|      * @param accessInfo Access info to the activity. | ||||
|      * @param entry Entry or record where the actions will be performed. | ||||
|      * @return Keyed with the action names and boolean to evalute if it can or cannot be done. | ||||
|      */ | ||||
|     getActions( | ||||
|         database: AddonModDataData, | ||||
|         accessInfo: AddonModDataGetDataAccessInformationWSResponse, | ||||
|         entry: AddonModDataEntry, | ||||
|     ):  Record<AddonModDataAction, boolean> { | ||||
|         return { | ||||
|             add: false, // Not directly used on entries.
 | ||||
|             more: true, | ||||
|             moreurl: true, | ||||
|             user: true, | ||||
|             userpicture: true, | ||||
|             timeadded: true, | ||||
|             timemodified: true, | ||||
|             tags: true, | ||||
| 
 | ||||
|             edit: entry.canmanageentry && !entry.deleted, // This already checks capabilities and readonly period.
 | ||||
|             delete: entry.canmanageentry, | ||||
|             approve: database.approval && accessInfo.canapprove && !entry.approved && !entry.deleted, | ||||
|             disapprove: database.approval && accessInfo.canapprove && entry.approved && !entry.deleted, | ||||
| 
 | ||||
|             approvalstatus: database.approval, | ||||
|             comments: database.comments, | ||||
| 
 | ||||
|             // Unsupported actions.
 | ||||
|             delcheck: false, | ||||
|             export: false, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convenience function to get the course id of the database. | ||||
|      * | ||||
|      * @param dataId Database id. | ||||
|      * @param courseId Course id, if known. | ||||
|      * @param siteId Site id, if not set, current site will be used. | ||||
|      * @return Resolved with course Id when done. | ||||
|      */ | ||||
|     protected async getActivityCourseIdIfNotSet(dataId: number, courseId?: number, siteId?: string): Promise<number> { | ||||
|         if (courseId) { | ||||
|             return courseId; | ||||
|         } | ||||
| 
 | ||||
|         const module = await CoreCourse.getModuleBasicInfoByInstance(dataId, 'data', siteId); | ||||
| 
 | ||||
|         return module.course; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the default template of a certain type. | ||||
|      * | ||||
|      * Based on Moodle function data_generate_default_template. | ||||
|      * | ||||
|      * @param type Type of template. | ||||
|      * @param fields List of database fields. | ||||
|      * @return Template HTML. | ||||
|      */ | ||||
|     getDefaultTemplate(type: AddonModDataTemplateType, fields: AddonModDataField[]): string { | ||||
|         if (type == AddonModDataTemplateType.LIST_HEADER || type == AddonModDataTemplateType.LIST_FOOTER) { | ||||
|             return ''; | ||||
|         } | ||||
| 
 | ||||
|         const html: string[] = []; | ||||
| 
 | ||||
|         if (type == AddonModDataTemplateType.LIST) { | ||||
|             html.push('##delcheck##<br />'); | ||||
|         } | ||||
| 
 | ||||
|         html.push( | ||||
|             '<div class="defaulttemplate">', | ||||
|             '<table class="mod-data-default-template ##approvalstatus##">', | ||||
|             '<tbody>', | ||||
|         ); | ||||
| 
 | ||||
|         fields.forEach((field) => { | ||||
|             html.push( | ||||
|                 '<tr class="">', | ||||
|                 '<td class="template-field cell c0" style="">', | ||||
|                 field.name, | ||||
|                 ': </td>', | ||||
|                 '<td class="template-token cell c1 lastcol" style="">[[', | ||||
|                 field.name, | ||||
|                 ']]</td>', | ||||
|                 '</tr>', | ||||
|             ); | ||||
|         }); | ||||
| 
 | ||||
|         if (type == AddonModDataTemplateType.LIST) { | ||||
|             html.push( | ||||
|                 '<tr class="lastrow">', | ||||
|                 '<td class="controls template-field cell c0 lastcol" style="" colspan="2">', | ||||
|                 '##edit##  ##more##  ##delete##  ##approve##  ##disapprove##  ##export##', | ||||
|                 '</td>', | ||||
|                 '</tr>', | ||||
|             ); | ||||
|         } else if (type == AddonModDataTemplateType.SINGLE) { | ||||
|             html.push( | ||||
|                 '<tr class="lastrow">', | ||||
|                 '<td class="controls template-field cell c0 lastcol" style="" colspan="2">', | ||||
|                 '##edit##  ##delete##  ##approve##  ##disapprove##  ##export##', | ||||
|                 '</td>', | ||||
|                 '</tr>', | ||||
|             ); | ||||
|         } else if (type == AddonModDataTemplateType.SEARCH) { | ||||
|             html.push( | ||||
|                 '<tr class="searchcontrols">', | ||||
|                 '<td class="template-field cell c0" style="">Author first name: </td>', | ||||
|                 '<td class="template-token cell c1 lastcol" style="">##firstname##</td>', | ||||
|                 '</tr>', | ||||
|                 '<tr class="searchcontrols lastrow">', | ||||
|                 '<td class="template-field cell c0" style="">Author surname: </td>', | ||||
|                 '<td class="template-token cell c1 lastcol" style="">##lastname##</td>', | ||||
|                 '</tr>', | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         html.push( | ||||
|             '</tbody>', | ||||
|             '</table>', | ||||
|             '</div>', | ||||
|         ); | ||||
| 
 | ||||
|         if (type == AddonModDataTemplateType.LIST) { | ||||
|             html.push('<hr />'); | ||||
|         } | ||||
| 
 | ||||
|         return html.join(''); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieve the entered data in the edit form. | ||||
|      * We don't use ng-model because it doesn't detect changes done by JavaScript. | ||||
|      * | ||||
|      * @param inputData Array with the entered form values. | ||||
|      * @param fields Fields that defines every content in the entry. | ||||
|      * @param dataId Database Id. If set, files will be uploaded and itemId set. | ||||
|      * @param entryId Entry Id. | ||||
|      * @param entryContents Original entry contents. | ||||
|      * @param offline True to prepare the data for an offline uploading, false otherwise. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return That contains object with the answers. | ||||
|      */ | ||||
|     async getEditDataFromForm( | ||||
|         inputData: CoreFormFields, | ||||
|         fields: AddonModDataField[], | ||||
|         dataId: number, | ||||
|         entryId: number, | ||||
|         entryContents: AddonModDataEntryFields, | ||||
|         offline: boolean = false, | ||||
|         siteId?: string, | ||||
|     ): Promise<AddonModDataEntryWSField[]> { | ||||
|         if (!inputData) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         // Filter and translate fields to each field plugin.
 | ||||
|         const entryFieldDataToSend: AddonModDataEntryWSField[] = []; | ||||
| 
 | ||||
|         const promises = fields.map(async (field) => { | ||||
|             const fieldData = AddonModDataFieldsDelegate.getFieldEditData(field, inputData, entryContents[field.id]); | ||||
|             if (!fieldData) { | ||||
|                 return; | ||||
|             } | ||||
|             const proms = fieldData.map(async (fieldSubdata) => { | ||||
|                 let value = fieldSubdata.value; | ||||
| 
 | ||||
|                 // Upload Files if asked.
 | ||||
|                 if (dataId && fieldSubdata.files) { | ||||
|                     value = await this.uploadOrStoreFiles( | ||||
|                         dataId, | ||||
|                         0, | ||||
|                         entryId, | ||||
|                         fieldSubdata.fieldid, | ||||
|                         fieldSubdata.files, | ||||
|                         offline, | ||||
|                         siteId, | ||||
|                     ); | ||||
|                 } | ||||
| 
 | ||||
|                 // WS wants values in JSON format.
 | ||||
|                 entryFieldDataToSend.push({ | ||||
|                     fieldid: fieldSubdata.fieldid, | ||||
|                     subfield: fieldSubdata.subfield || '', | ||||
|                     value: fieldSubdata.value ? JSON.stringify(value) : '', | ||||
|                 }); | ||||
| 
 | ||||
|                 return; | ||||
|             }); | ||||
| 
 | ||||
|             await Promise.all(proms); | ||||
|         }); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
| 
 | ||||
|         return entryFieldDataToSend; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieve the temp files to be updated. | ||||
|      * | ||||
|      * @param inputData Array with the entered form values. | ||||
|      * @param fields Fields that defines every content in the entry. | ||||
|      * @param entryContents Original entry contents indexed by field id. | ||||
|      * @return That contains object with the files. | ||||
|      */ | ||||
|     async getEditTmpFiles( | ||||
|         inputData: CoreFormFields, | ||||
|         fields: AddonModDataField[], | ||||
|         entryContents: AddonModDataEntryFields, | ||||
|     ): Promise<(CoreWSExternalFile | FileEntry)[]> { | ||||
|         if (!inputData) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         // Filter and translate fields to each field plugin.
 | ||||
|         const promises = fields.map((field) => | ||||
|             AddonModDataFieldsDelegate.getFieldEditFiles(field, inputData, entryContents[field.id])); | ||||
| 
 | ||||
|         const fieldsFiles = await Promise.all(promises); | ||||
| 
 | ||||
|         return fieldsFiles.reduce((files, fieldFiles) => files.concat(fieldFiles), []); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a list of stored attachment files for a new entry. See $mmaModDataHelper#storeFiles. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param entryId Entry ID or, if creating, timemodified. | ||||
|      * @param fieldId Field ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the files. | ||||
|      */ | ||||
|     async getStoredFiles(dataId: number, entryId: number, fieldId: number, siteId?: string): Promise<FileEntry[]> { | ||||
|         const folderPath = await AddonModDataOffline.getEntryFieldFolder(dataId, entryId, fieldId, siteId); | ||||
| 
 | ||||
|         try { | ||||
|             return CoreFileUploader.getStoredFiles(folderPath); | ||||
|         } catch { | ||||
|             // Ignore not found files.
 | ||||
|             return []; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the template of a certain type. | ||||
|      * | ||||
|      * @param data Database object. | ||||
|      * @param type Type of template. | ||||
|      * @param fields List of database fields. | ||||
|      * @return Template HTML. | ||||
|      */ | ||||
|     getTemplate(data: AddonModDataData, type: AddonModDataTemplateType, fields: AddonModDataField[]): string { | ||||
|         let template = data[type] || this.getDefaultTemplate(type, fields); | ||||
| 
 | ||||
|         if (type != AddonModDataTemplateType.LIST_HEADER && type != AddonModDataTemplateType.LIST_FOOTER) { | ||||
|             // Try to fix syntax errors so the template can be parsed by Angular.
 | ||||
|             template = CoreDomUtils.fixHtml(template); | ||||
|         } | ||||
| 
 | ||||
|         // Add core-link directive to links.
 | ||||
|         template = template.replace( | ||||
|             /<a ([^>]*href="[^>]*)>/ig, | ||||
|             (match, attributes) => '<a core-link capture="true" ' + attributes + '>', | ||||
|         ); | ||||
| 
 | ||||
|         return template; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if data has been changed by the user. | ||||
|      * | ||||
|      * @param inputData Object with the entered form values. | ||||
|      * @param fields Fields that defines every content in the entry. | ||||
|      * @param dataId Database Id. If set, fils will be uploaded and itemId set. | ||||
|      * @param entryContents Original entry contents indexed by field id. | ||||
|      * @return True if changed, false if not. | ||||
|      */ | ||||
|     hasEditDataChanged( | ||||
|         inputData: CoreFormFields, | ||||
|         fields: AddonModDataField[], | ||||
|         entryContents: AddonModDataEntryFields, | ||||
|     ): boolean { | ||||
|         return fields.some((field) => | ||||
|             AddonModDataFieldsDelegate.hasFieldDataChanged(field, inputData, entryContents[field.id])); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Displays a confirmation modal for deleting an entry. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param entryId Entry ID. | ||||
|      * @param courseId Course ID. It not defined, it will be fetched. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      */ | ||||
|     async showDeleteEntryModal(dataId: number, entryId: number, courseId?: number, siteId?: string): Promise<void> { | ||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         try { | ||||
|             await CoreDomUtils.showDeleteConfirm('addon.mod_data.confirmdeleterecord'); | ||||
| 
 | ||||
|             const modal = await CoreDomUtils.showModalLoading(); | ||||
| 
 | ||||
|             try { | ||||
|                 if (entryId > 0) { | ||||
|                     courseId = await this.getActivityCourseIdIfNotSet(dataId, courseId, siteId); | ||||
|                 } | ||||
| 
 | ||||
|                 AddonModData.deleteEntry(dataId, entryId, courseId!, siteId); | ||||
|             } catch (message) { | ||||
|                 CoreDomUtils.showErrorModalDefault(message, 'addon.mod_data.errordeleting', true); | ||||
| 
 | ||||
|                 modal.dismiss(); | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             try { | ||||
|                 await AddonModData.invalidateEntryData(dataId, entryId, siteId); | ||||
|                 await AddonModData.invalidateEntriesData(dataId, siteId); | ||||
|             } catch (error) { | ||||
|                 // Ignore errors.
 | ||||
|             } | ||||
| 
 | ||||
|             CoreEvents.trigger(AddonModDataProvider.ENTRY_CHANGED, { dataId, entryId,  deleted: true }, siteId); | ||||
| 
 | ||||
|             CoreDomUtils.showToast('addon.mod_data.recorddeleted', true, 3000); | ||||
| 
 | ||||
|             modal.dismiss(); | ||||
|         } catch { | ||||
|             // Ignore error, it was already displayed.
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a list of files (either online files or local files), store the local files in a local folder | ||||
|      * to be submitted later. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param entryId Entry ID or, if creating, timemodified. | ||||
|      * @param fieldId Field ID. | ||||
|      * @param files List of files. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved if success, rejected otherwise. | ||||
|      */ | ||||
|     async storeFiles( | ||||
|         dataId: number, | ||||
|         entryId: number, | ||||
|         fieldId: number, | ||||
|         files: (CoreWSExternalFile | FileEntry)[], | ||||
|         siteId?: string, | ||||
|     ): Promise<CoreFileUploaderStoreFilesResult> { | ||||
|         // Get the folder where to store the files.
 | ||||
|         const folderPath = await AddonModDataOffline.getEntryFieldFolder(dataId, entryId, fieldId, siteId); | ||||
| 
 | ||||
|         return CoreFileUploader.storeFilesToUpload(folderPath, files); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Upload or store some files, depending if the user is offline or not. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param itemId Draft ID to use. Undefined or 0 to create a new draft ID. | ||||
|      * @param entryId Entry ID or, if creating, timemodified. | ||||
|      * @param fieldId Field ID. | ||||
|      * @param files List of files. | ||||
|      * @param offline True if files sould be stored for offline, false to upload them. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the itemId for the uploaded file/s. | ||||
|      */ | ||||
|     async uploadOrStoreFiles( | ||||
|         dataId: number, | ||||
|         itemId: number = 0, | ||||
|         entryId: number, | ||||
|         fieldId: number, | ||||
|         files: (CoreWSExternalFile | FileEntry)[], | ||||
|         offline: boolean, | ||||
|         siteId?: string, | ||||
|     ): Promise<number | CoreFileUploaderStoreFilesResult> { | ||||
|         if (!files.length) { | ||||
|             return 0; | ||||
|         } | ||||
| 
 | ||||
|         if (offline) { | ||||
|             return this.storeFiles(dataId, entryId, fieldId, files, siteId); | ||||
|         } | ||||
| 
 | ||||
|         return CoreFileUploader.uploadOrReuploadFiles(files, AddonModDataProvider.COMPONENT, itemId, siteId); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataHelper = makeSingleton(AddonModDataHelperProvider); | ||||
							
								
								
									
										290
									
								
								src/addons/mod/data/services/data-offline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								src/addons/mod/data/services/data-offline.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,290 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader'; | ||||
| import { CoreFile } from '@services/file'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModDataAction, AddonModDataEntryWSField } from './data'; | ||||
| import { AddonModDataEntryDBRecord, DATA_ENTRY_TABLE } from './database/data'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle Offline data. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataOfflineProvider { | ||||
| 
 | ||||
|     /** | ||||
|      * Delete all the actions of an entry. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param entryId Database entry ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved if deleted, rejected if failure. | ||||
|      */ | ||||
|     async deleteAllEntryActions(dataId: number, entryId: number, siteId?: string): Promise<void> { | ||||
|         const actions = await this.getEntryActions(dataId, entryId, siteId); | ||||
| 
 | ||||
|         const promises = actions.map((action) => { | ||||
|             this.deleteEntry(dataId, entryId, action.action, siteId); | ||||
|         }); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete an stored entry. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param entryId Database entry Id. | ||||
|      * @param action Action to be done | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved if deleted, rejected if failure. | ||||
|      */ | ||||
|     async deleteEntry(dataId: number, entryId: number, action: AddonModDataAction, siteId?: string): Promise<void> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         await this.deleteEntryFiles(dataId, entryId, action, site.id); | ||||
| 
 | ||||
|         await site.getDb().deleteRecords(DATA_ENTRY_TABLE, { | ||||
|             dataid: dataId, | ||||
|             entryid: entryId, | ||||
|             action, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete entry offline files. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param entryId Database entry ID. | ||||
|      * @param action Action to be done. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved if deleted, rejected if failure. | ||||
|      */ | ||||
|     protected async deleteEntryFiles(dataId: number, entryId: number, action: AddonModDataAction, siteId?: string): Promise<void> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
|         const entry = await CoreUtils.ignoreErrors(this.getEntry(dataId, entryId, action, site.id)); | ||||
| 
 | ||||
|         if (!entry || !entry.fields) { | ||||
|             // Entry not found or no fields, ignore.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         entry.fields.forEach((field) => { | ||||
|             const value = CoreTextUtils.parseJSON<CoreFileUploaderStoreFilesResult>(field.value); | ||||
| 
 | ||||
|             if (!value.offline) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const promise = this.getEntryFieldFolder(dataId, entryId, field.fieldid, site.id).then((folderPath) => | ||||
|                 CoreFileUploader.getStoredFiles(folderPath)).then((files) => | ||||
|                 CoreFileUploader.clearTmpFiles(files)).catch(() => { // Files not found, ignore.
 | ||||
|             }); | ||||
| 
 | ||||
|             promises.push(promise); | ||||
|         }); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get all the stored entry data from all the databases. | ||||
|      * | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with entries. | ||||
|      */ | ||||
|     async getAllEntries(siteId?: string): Promise<AddonModDataOfflineAction[]> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
|         const entries = await site.getDb().getAllRecords<AddonModDataEntryDBRecord>(DATA_ENTRY_TABLE); | ||||
| 
 | ||||
|         return entries.map(this.parseRecord.bind(this)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get all the stored entry actions from a certain database, sorted by modification time. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with entries. | ||||
|      */ | ||||
|     async getDatabaseEntries(dataId: number, siteId?: string): Promise<AddonModDataOfflineAction[]> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
|         const entries = await site.getDb().getRecords<AddonModDataEntryDBRecord>( | ||||
|             DATA_ENTRY_TABLE, | ||||
|             { dataid: dataId }, | ||||
|             'timemodified', | ||||
|         ); | ||||
| 
 | ||||
|         return entries.map(this.parseRecord.bind(this)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get an stored entry data. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param entryId Database entry Id. | ||||
|      * @param action Action to be done | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with entry. | ||||
|      */ | ||||
|     async getEntry( | ||||
|         dataId: number, | ||||
|         entryId: number, | ||||
|         action: AddonModDataAction, | ||||
|         siteId?: string, | ||||
|     ): Promise<AddonModDataOfflineAction> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         const entry = await site.getDb().getRecord<AddonModDataEntryDBRecord>(DATA_ENTRY_TABLE, { | ||||
|             dataid: dataId, entryid: entryId, | ||||
|             action, | ||||
|         }); | ||||
| 
 | ||||
|         return this.parseRecord(entry); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get an all stored entry actions data. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param entryId Database entry Id. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with entry actions. | ||||
|      */ | ||||
|     async getEntryActions(dataId: number, entryId: number, siteId?: string): Promise<AddonModDataOfflineAction[]> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
|         const entries = await site.getDb().getRecords<AddonModDataEntryDBRecord>( | ||||
|             DATA_ENTRY_TABLE, | ||||
|             { dataid: dataId, entryid: entryId }, | ||||
|         ); | ||||
| 
 | ||||
|         return entries.map(this.parseRecord.bind(this)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if there are offline entries to send. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with boolean: true if has offline answers, false otherwise. | ||||
|      */ | ||||
|     async hasOfflineData(dataId: number, siteId?: string): Promise<boolean> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         return CoreUtils.promiseWorks( | ||||
|             site.getDb().recordExists(DATA_ENTRY_TABLE, { dataid: dataId }), | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the path to the folder where to store files for offline files in a database. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the path. | ||||
|      */ | ||||
|     protected async getDatabaseFolder(dataId: number, siteId?: string): Promise<string> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
|         const siteFolderPath = CoreFile.getSiteFolder(site.getId()); | ||||
|         const folderPath = 'offlinedatabase/' + dataId; | ||||
| 
 | ||||
|         return CoreTextUtils.concatenatePaths(siteFolderPath, folderPath); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the path to the folder where to store files for a new offline entry. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param entryId The ID of the entry. | ||||
|      * @param fieldId Field ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the path. | ||||
|      */ | ||||
|     async getEntryFieldFolder(dataId: number, entryId: number, fieldId: number, siteId?: string): Promise<string> { | ||||
|         const folderPath = await this.getDatabaseFolder(dataId, siteId); | ||||
| 
 | ||||
|         return CoreTextUtils.concatenatePaths(folderPath, entryId + '_' + fieldId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Parse "fields" of an offline record. | ||||
|      * | ||||
|      * @param record Record object | ||||
|      * @return Record object with columns parsed. | ||||
|      */ | ||||
|     protected parseRecord(record: AddonModDataEntryDBRecord): AddonModDataOfflineAction { | ||||
|         return Object.assign(record, { | ||||
|             fields: CoreTextUtils.parseJSON<AddonModDataEntryWSField[]>(record.fields), | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Save an entry data to be sent later. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param entryId Database entry Id. If action is add entryId should be 0 and -timemodified will be used. | ||||
|      * @param action Action to be done to the entry: [add, edit, delete, approve, disapprove] | ||||
|      * @param courseId Course ID of the database. | ||||
|      * @param groupId Group ID. Only provided when adding. | ||||
|      * @param fields Array of field data of the entry if needed. | ||||
|      * @param timemodified The time the entry was modified. If not defined, current time. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved if stored, rejected if failure. | ||||
|      */ | ||||
|     async saveEntry( | ||||
|         dataId: number, | ||||
|         entryId: number, | ||||
|         action: AddonModDataAction, | ||||
|         courseId: number, | ||||
|         groupId = 0, | ||||
|         fields?: AddonModDataEntryWSField[], | ||||
|         timemodified?: number, | ||||
|         siteId?: string, | ||||
|     ): Promise<AddonModDataEntryDBRecord> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         timemodified = timemodified || new Date().getTime(); | ||||
|         entryId = typeof entryId == 'undefined' || entryId === null ? -timemodified : entryId; | ||||
| 
 | ||||
|         const entry: AddonModDataEntryDBRecord = { | ||||
|             dataid: dataId, | ||||
|             courseid: courseId, | ||||
|             groupid: groupId, | ||||
|             action, | ||||
|             entryid: entryId, | ||||
|             fields: JSON.stringify(fields || []), | ||||
|             timemodified, | ||||
|         }; | ||||
| 
 | ||||
|         await site.getDb().insertRecord(DATA_ENTRY_TABLE, entry); | ||||
| 
 | ||||
|         return entry; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataOffline = makeSingleton(AddonModDataOfflineProvider); | ||||
| 
 | ||||
| /** | ||||
|  * Entry action stored offline. | ||||
|  */ | ||||
| export type AddonModDataOfflineAction = Omit<AddonModDataEntryDBRecord, 'fields'> & { | ||||
|     fields: AddonModDataEntryWSField[]; | ||||
| }; | ||||
							
								
								
									
										499
									
								
								src/addons/mod/data/services/data-sync.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										499
									
								
								src/addons/mod/data/services/data-sync.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,499 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { ContextLevel } from '@/core/constants'; | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreSyncBlockedError } from '@classes/base-sync'; | ||||
| import { CoreNetworkError } from '@classes/errors/network-error'; | ||||
| import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync'; | ||||
| import { CoreCourseCommonModWSOptions } from '@features/course/services/course'; | ||||
| import { CoreCourseLogHelper } from '@features/course/services/log-helper'; | ||||
| import { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader'; | ||||
| import { CoreRatingSync } from '@features/rating/services/rating-sync'; | ||||
| import { FileEntry } from '@ionic-native/file'; | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; | ||||
| import { CoreSync } from '@services/sync'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreWSExternalFile } from '@services/ws'; | ||||
| import { Translate, makeSingleton } from '@singletons'; | ||||
| import { CoreEvents } from '@singletons/events'; | ||||
| import { AddonModDataProvider, AddonModData, AddonModDataData, AddonModDataAction } from './data'; | ||||
| import { AddonModDataHelper } from './data-helper'; | ||||
| import { AddonModDataOffline, AddonModDataOfflineAction } from './data-offline'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to sync databases. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataSyncProvider extends CoreCourseActivitySyncBaseProvider<AddonModDataSyncResult> { | ||||
| 
 | ||||
|     static readonly AUTO_SYNCED = 'addon_mod_data_autom_synced'; | ||||
| 
 | ||||
|     protected componentTranslatableString = 'data'; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super('AddonModDataSyncProvider'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a database has data to synchronize. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with boolean: true if has data to sync, false otherwise. | ||||
|      */ | ||||
|     hasDataToSync(dataId: number, siteId?: string): Promise<boolean> { | ||||
|         return AddonModDataOffline.hasOfflineData(dataId, siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Try to synchronize all the databases in a certain site or in all sites. | ||||
|      * | ||||
|      * @param siteId Site ID to sync. If not defined, sync all sites. | ||||
|      * @param force Wether to force sync not depending on last execution. | ||||
|      * @return Promise resolved if sync is successful, rejected if sync fails. | ||||
|      */ | ||||
|     syncAllDatabases(siteId?: string, force?: boolean): Promise<void> { | ||||
|         return this.syncOnSites('all databases', this.syncAllDatabasesFunc.bind(this, !!force), siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sync all pending databases on a site. | ||||
|      * | ||||
|      * @param force Wether to force sync not depending on last execution. | ||||
|      * @param siteId Site ID to sync. If not defined, sync all sites. | ||||
|      * @param Promise resolved if sync is successful, rejected if sync fails. | ||||
|      */ | ||||
|     protected async syncAllDatabasesFunc(force: boolean, siteId: string): Promise<void> { | ||||
|         const promises: Promise<unknown>[] = []; | ||||
| 
 | ||||
|         // Get all data answers pending to be sent in the site.
 | ||||
|         promises.push(AddonModDataOffline.getAllEntries(siteId).then(async (offlineActions) => { | ||||
|             // Get data id.
 | ||||
|             let dataIds: number[] = offlineActions.map((action) => action.dataid); | ||||
|             // Get unique values.
 | ||||
|             dataIds = dataIds.filter((id, pos) => dataIds.indexOf(id) == pos); | ||||
| 
 | ||||
|             const entriesPromises = dataIds.map(async (dataId) => { | ||||
|                 const result = force | ||||
|                     ? await this.syncDatabase(dataId, siteId) | ||||
|                     : await this.syncDatabaseIfNeeded(dataId, siteId); | ||||
| 
 | ||||
|                 if (result && result.updated) { | ||||
|                     // Sync done. Send event.
 | ||||
|                     CoreEvents.trigger(AddonModDataSyncProvider.AUTO_SYNCED, { | ||||
|                         dataId: dataId, | ||||
|                         warnings: result.warnings, | ||||
|                     }, siteId); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             await Promise.all(entriesPromises); | ||||
| 
 | ||||
|             return; | ||||
|         })); | ||||
| 
 | ||||
|         promises.push(this.syncRatings(undefined, force, siteId)); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sync a database only if a certain time has passed since the last time. | ||||
|      * | ||||
|      * @param dataId Database ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when the data is synced or if it doesn't need to be synced. | ||||
|      */ | ||||
|     async syncDatabaseIfNeeded(dataId: number, siteId?: string): Promise<AddonModDataSyncResult | undefined> { | ||||
|         const needed = await this.isSyncNeeded(dataId, siteId); | ||||
| 
 | ||||
|         if (needed) { | ||||
|             return this.syncDatabase(dataId, siteId); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Synchronize a data. | ||||
|      * | ||||
|      * @param dataId Data ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved if sync is successful, rejected otherwise. | ||||
|      */ | ||||
|     syncDatabase(dataId: number, siteId?: string): Promise<AddonModDataSyncResult> { | ||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         if (this.isSyncing(dataId, siteId)) { | ||||
|             // There's already a sync ongoing for this database, return the promise.
 | ||||
|             return this.getOngoingSync(dataId, siteId)!; | ||||
|         } | ||||
| 
 | ||||
|         // Verify that database isn't blocked.
 | ||||
|         if (CoreSync.isBlocked(AddonModDataProvider.COMPONENT, dataId, siteId)) { | ||||
|             this.logger.debug(`Cannot sync database '${dataId}' because it is blocked.`); | ||||
| 
 | ||||
|             throw new CoreSyncBlockedError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate })); | ||||
|         } | ||||
| 
 | ||||
|         this.logger.debug(`Try to sync data '${dataId}' in site ${siteId}'`); | ||||
| 
 | ||||
|         const syncPromise = this.performSyncDatabase(dataId, siteId); | ||||
| 
 | ||||
|         return this.addOngoingSync(dataId, syncPromise, siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Perform the database syncronization. | ||||
|      * | ||||
|      * @param dataId Data ID. | ||||
|      * @param siteId Site ID. | ||||
|      * @return Promise resolved if sync is successful, rejected otherwise. | ||||
|      */ | ||||
|     protected async performSyncDatabase(dataId: number, siteId: string): Promise<AddonModDataSyncResult> { | ||||
|         // Sync offline logs.
 | ||||
|         await CoreUtils.ignoreErrors( | ||||
|             CoreCourseLogHelper.syncActivity(AddonModDataProvider.COMPONENT, dataId, siteId), | ||||
|         ); | ||||
| 
 | ||||
|         const result: AddonModDataSyncResult = { | ||||
|             warnings: [], | ||||
|             updated: false, | ||||
|         }; | ||||
| 
 | ||||
|         // Get answers to be sent.
 | ||||
|         const offlineActions: AddonModDataOfflineAction[] = | ||||
|             await CoreUtils.ignoreErrors(AddonModDataOffline.getDatabaseEntries(dataId, siteId), []); | ||||
| 
 | ||||
|         if (!offlineActions.length) { | ||||
|             // Nothing to sync.
 | ||||
|             await CoreUtils.ignoreErrors(this.setSyncTime(dataId, siteId)); | ||||
| 
 | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         if (!CoreApp.isOnline()) { | ||||
|             // Cannot sync in offline.
 | ||||
|             throw new CoreNetworkError(); | ||||
|         } | ||||
| 
 | ||||
|         const courseId = offlineActions[0].courseid; | ||||
| 
 | ||||
|         // Send the answers.
 | ||||
|         const database = await AddonModData.getDatabaseById(courseId, dataId, { siteId }); | ||||
| 
 | ||||
|         const offlineEntries: Record<number, AddonModDataOfflineAction[]> = {}; | ||||
| 
 | ||||
|         offlineActions.forEach((entry) => { | ||||
|             if (typeof offlineEntries[entry.entryid] == 'undefined') { | ||||
|                 offlineEntries[entry.entryid] = []; | ||||
|             } | ||||
| 
 | ||||
|             offlineEntries[entry.entryid].push(entry); | ||||
|         }); | ||||
| 
 | ||||
|         const promises = CoreUtils.objectToArray(offlineEntries).map((entryActions) => | ||||
|             this.syncEntry(database, entryActions, result, siteId)); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
| 
 | ||||
|         if (result.updated) { | ||||
|             // Data has been sent to server. Now invalidate the WS calls.
 | ||||
|             await CoreUtils.ignoreErrors(AddonModData.invalidateContent(database.coursemodule, courseId, siteId)); | ||||
|         } | ||||
| 
 | ||||
|         // Sync finished, set sync time.
 | ||||
|         await CoreUtils.ignoreErrors(this.setSyncTime(dataId, siteId)); | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Synchronize an entry. | ||||
|      * | ||||
|      * @param database Database. | ||||
|      * @param entryActions Entry actions. | ||||
|      * @param result Object with the result of the sync. | ||||
|      * @param siteId Site ID. | ||||
|      * @return Promise resolved if success, rejected otherwise. | ||||
|      */ | ||||
|     protected async syncEntry( | ||||
|         database: AddonModDataData, | ||||
|         entryActions: AddonModDataOfflineAction[], | ||||
|         result: AddonModDataSyncResult, | ||||
|         siteId: string, | ||||
|     ): Promise<void> { | ||||
|         const synEntryResult = await this.performSyncEntry(database, entryActions, result, siteId); | ||||
| 
 | ||||
|         if (synEntryResult.discardError) { | ||||
|             // Submission was discarded, add a warning.
 | ||||
|             const message = Translate.instant('core.warningofflinedatadeleted', { | ||||
|                 component: this.componentTranslate, | ||||
|                 name: database.name, | ||||
|                 error: synEntryResult.discardError, | ||||
|             }); | ||||
| 
 | ||||
|             if (result.warnings.indexOf(message) == -1) { | ||||
|                 result.warnings.push(message); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Sync done. Send event.
 | ||||
|         CoreEvents.trigger(AddonModDataSyncProvider.AUTO_SYNCED, { | ||||
|             dataId: database.id, | ||||
|             entryId: synEntryResult.entryId, | ||||
|             offlineEntryId: synEntryResult.offlineId, | ||||
|             warnings: result.warnings, | ||||
|             deleted: synEntryResult.deleted, | ||||
|         }, siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Perform the synchronization of an entry. | ||||
|      * | ||||
|      * @param database Database. | ||||
|      * @param entryActions Entry actions. | ||||
|      * @param result Object with the result of the sync. | ||||
|      * @param siteId Site ID. | ||||
|      * @return Promise resolved if success, rejected otherwise. | ||||
|      */ | ||||
|     protected async performSyncEntry( | ||||
|         database: AddonModDataData, | ||||
|         entryActions: AddonModDataOfflineAction[], | ||||
|         result: AddonModDataSyncResult, | ||||
|         siteId: string, | ||||
|     ): Promise<AddonModDataSyncEntryResult> { | ||||
|         let entryId = entryActions[0].entryid; | ||||
| 
 | ||||
|         const entryResult: AddonModDataSyncEntryResult = { | ||||
|             deleted: false, | ||||
|             entryId: entryId, | ||||
|         }; | ||||
| 
 | ||||
|         const editAction = entryActions.find((action) => | ||||
|             action.action == AddonModDataAction.ADD || action.action == AddonModDataAction.EDIT); | ||||
|         const approveAction = entryActions.find((action) => | ||||
|             action.action == AddonModDataAction.APPROVE || action.action == AddonModDataAction.DISAPPROVE); | ||||
|         const deleteAction = entryActions.find((action) => action.action == AddonModDataAction.DELETE); | ||||
| 
 | ||||
|         const options: CoreCourseCommonModWSOptions = { | ||||
|             cmId: database.coursemodule, | ||||
|             readingStrategy: CoreSitesReadingStrategy.OnlyNetwork, | ||||
|             siteId, | ||||
|         }; | ||||
| 
 | ||||
|         let timemodified = 0; | ||||
|         if (entryId > 0) { | ||||
|             try { | ||||
|                 const entry = await AddonModData.getEntry(database.id, entryId, options); | ||||
| 
 | ||||
|                 timemodified = entry.entry.timemodified; | ||||
|             } catch (error) { | ||||
|                 if (error && CoreUtils.isWebServiceError(error)) { | ||||
|                     // The WebService has thrown an error, this means the entry has been deleted.
 | ||||
|                     timemodified = -1; | ||||
|                 } else { | ||||
|                     throw error; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|         } else if (editAction) { | ||||
|             // New entry.
 | ||||
|             entryResult.offlineId = entryId; | ||||
|             timemodified = 0; | ||||
|         } else { | ||||
|             // New entry but the add action is missing, discard.
 | ||||
|             timemodified = -1; | ||||
|         } | ||||
| 
 | ||||
|         if (timemodified < 0 || timemodified >= entryActions[0].timemodified) { | ||||
|             // The entry was not found in Moodle or the entry has been modified, discard the action.
 | ||||
|             result.updated = true; | ||||
|             entryResult.discardError = Translate.instant('addon.mod_data.warningsubmissionmodified'); | ||||
| 
 | ||||
|             await AddonModDataOffline.deleteAllEntryActions(database.id, entryId, siteId); | ||||
| 
 | ||||
|             return entryResult; | ||||
|         } | ||||
| 
 | ||||
|         if (deleteAction) { | ||||
|             try { | ||||
|                 await AddonModData.deleteEntryOnline(entryId, siteId); | ||||
|                 entryResult.deleted = true; | ||||
|             } catch (error) { | ||||
|                 if (error && CoreUtils.isWebServiceError(error)) { | ||||
|                     // The WebService has thrown an error, this means it cannot be performed. Discard.
 | ||||
|                     entryResult.discardError = CoreTextUtils.getErrorMessageFromError(error); | ||||
|                 } else { | ||||
|                     // Couldn't connect to server, reject.
 | ||||
|                     throw error; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // Delete the offline data.
 | ||||
|             result.updated = true; | ||||
| 
 | ||||
|             await AddonModDataOffline.deleteAllEntryActions(deleteAction.dataid, deleteAction.entryid, siteId); | ||||
| 
 | ||||
|             return entryResult; | ||||
|         } | ||||
| 
 | ||||
|         if (editAction) { | ||||
|             try { | ||||
|                 await Promise.all(editAction.fields.map(async (field) => { | ||||
|                     // Upload Files if asked.
 | ||||
|                     const value = CoreTextUtils.parseJSON<CoreFileUploaderStoreFilesResult>(field.value || ''); | ||||
|                     if (value.online || value.offline) { | ||||
|                         let files: (CoreWSExternalFile | FileEntry)[] = value.online || []; | ||||
| 
 | ||||
|                         const offlineFiles = value.offline | ||||
|                             ? await AddonModDataHelper.getStoredFiles(editAction.dataid, entryId, field.fieldid) | ||||
|                             : []; | ||||
| 
 | ||||
|                         files = files.concat(offlineFiles); | ||||
| 
 | ||||
|                         const filesResult = await AddonModDataHelper.uploadOrStoreFiles( | ||||
|                             editAction.dataid, | ||||
|                             0, | ||||
|                             entryId, | ||||
|                             field.fieldid, | ||||
|                             files, | ||||
|                             false, | ||||
|                             siteId, | ||||
|                         ); | ||||
| 
 | ||||
|                         field.value = JSON.stringify(filesResult); | ||||
|                     } | ||||
|                 })); | ||||
| 
 | ||||
|                 if (editAction.action == AddonModDataAction.ADD) { | ||||
|                     const result = await AddonModData.addEntryOnline( | ||||
|                         editAction.dataid, | ||||
|                         editAction.fields, | ||||
|                         editAction.groupid, | ||||
|                         siteId, | ||||
|                     ); | ||||
|                     entryId = result.newentryid; | ||||
|                     entryResult.entryId = entryId; | ||||
|                 } else { | ||||
|                     await AddonModData.editEntryOnline(entryId, editAction.fields, siteId); | ||||
|                 } | ||||
|             } catch (error) { | ||||
|                 if (error && CoreUtils.isWebServiceError(error)) { | ||||
|                     // The WebService has thrown an error, this means it cannot be performed. Discard.
 | ||||
|                     entryResult.discardError = CoreTextUtils.getErrorMessageFromError(error); | ||||
|                 } else { | ||||
|                     // Couldn't connect to server, reject.
 | ||||
|                     throw error; | ||||
|                 } | ||||
|             } | ||||
|             // Delete the offline data.
 | ||||
|             result.updated = true; | ||||
| 
 | ||||
|             await AddonModDataOffline.deleteEntry(editAction.dataid, editAction.entryid, editAction.action, siteId); | ||||
|         } | ||||
| 
 | ||||
|         if (approveAction) { | ||||
|             try { | ||||
|                 await AddonModData.approveEntryOnline(entryId, approveAction.action == AddonModDataAction.APPROVE, siteId); | ||||
|             } catch (error) { | ||||
|                 if (error && CoreUtils.isWebServiceError(error)) { | ||||
|                     // The WebService has thrown an error, this means it cannot be performed. Discard.
 | ||||
|                     entryResult.discardError = CoreTextUtils.getErrorMessageFromError(error); | ||||
|                 } else { | ||||
|                     // Couldn't connect to server, reject.
 | ||||
|                     throw error; | ||||
|                 } | ||||
|             } | ||||
|             // Delete the offline data.
 | ||||
|             result.updated = true; | ||||
| 
 | ||||
|             await AddonModDataOffline.deleteEntry(approveAction.dataid, approveAction.entryid, approveAction.action, siteId); | ||||
|         } | ||||
| 
 | ||||
|         return entryResult; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Synchronize offline ratings. | ||||
|      * | ||||
|      * @param cmId Course module to be synced. If not defined, sync all databases. | ||||
|      * @param force Wether to force sync not depending on last execution. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved if sync is successful, rejected otherwise. | ||||
|      */ | ||||
|     async syncRatings(cmId?: number, force?: boolean, siteId?: string): Promise<AddonModDataSyncResult> { | ||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         const results = await CoreRatingSync.syncRatings('mod_data', 'entry', ContextLevel.MODULE, cmId, 0, force, siteId); | ||||
|         let updated = false; | ||||
|         const warnings = []; | ||||
| 
 | ||||
|         const promises = results.map((result) => | ||||
|             AddonModData.getDatabase(result.itemSet.courseId, result.itemSet.instanceId, { siteId }) | ||||
|                 .then((database) => { | ||||
|                     const subPromises: Promise<void>[] = []; | ||||
| 
 | ||||
|                     if (result.updated.length) { | ||||
|                         updated = true; | ||||
| 
 | ||||
|                         // Invalidate entry of updated ratings.
 | ||||
|                         result.updated.forEach((itemId) => { | ||||
|                             subPromises.push(AddonModData.invalidateEntryData(database.id, itemId, siteId)); | ||||
|                         }); | ||||
|                     } | ||||
| 
 | ||||
|                     if (result.warnings.length) { | ||||
|                         result.warnings.forEach((warning) => { | ||||
|                             this.addOfflineDataDeletedWarning(warnings, database.name, warning); | ||||
|                         }); | ||||
|                     } | ||||
| 
 | ||||
|                     return CoreUtils.allPromises(subPromises); | ||||
|                 })); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
| 
 | ||||
|         return ({ updated, warnings }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataSync = makeSingleton(AddonModDataSyncProvider); | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by a database sync. | ||||
|  */ | ||||
| export type AddonModDataSyncEntryResult = { | ||||
|     discardError?: string; | ||||
|     offlineId?: number; | ||||
|     entryId: number; | ||||
|     deleted: boolean; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by a database sync. | ||||
|  */ | ||||
| export type AddonModDataSyncResult = { | ||||
|     warnings: string[]; // List of warnings.
 | ||||
|     updated: boolean; // Whether some data was sent to the server or offline data was updated.
 | ||||
| }; | ||||
| 
 | ||||
| export type AddonModDataAutoSyncData =  { | ||||
|     dataId: number; | ||||
|     warnings: string[]; | ||||
|     entryId?: number; | ||||
|     offlineEntryId?: number; | ||||
|     deleted?: boolean; | ||||
| }; | ||||
							
								
								
									
										1460
									
								
								src/addons/mod/data/services/data.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1460
									
								
								src/addons/mod/data/services/data.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										83
									
								
								src/addons/mod/data/services/database/data.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/addons/mod/data/services/database/data.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { SQLiteDB } from '@classes/sqlitedb'; | ||||
| import { CoreSiteSchema } from '@services/sites'; | ||||
| import { AddonModDataAction } from '../data'; | ||||
| 
 | ||||
| /** | ||||
|  * Database variables for AddonModDataOfflineProvider. | ||||
|  */ | ||||
| export const DATA_ENTRY_TABLE = 'addon_mod_data_entry_1'; | ||||
| export const ADDON_MOD_DATA_OFFLINE_SITE_SCHEMA: CoreSiteSchema = { | ||||
|     name: 'AddonModDataOfflineProvider', | ||||
|     version: 1, | ||||
|     tables: [ | ||||
|         { | ||||
|             name: DATA_ENTRY_TABLE, | ||||
|             columns: [ | ||||
|                 { | ||||
|                     name: 'dataid', | ||||
|                     type: 'INTEGER', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'courseid', | ||||
|                     type: 'INTEGER', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'groupid', | ||||
|                     type: 'INTEGER', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'action', | ||||
|                     type: 'TEXT', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'entryid', | ||||
|                     type: 'INTEGER', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'fields', | ||||
|                     type: 'TEXT', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'timemodified', | ||||
|                     type: 'INTEGER', | ||||
|                 }, | ||||
|             ], | ||||
|             primaryKeys: ['dataid', 'entryid', 'action'], | ||||
|         }, | ||||
|     ], | ||||
|     async migrate(db: SQLiteDB, oldVersion: number): Promise<void> { | ||||
|         if (oldVersion > 0) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Move the records from the old table.
 | ||||
|         await db.migrateTable('addon_mod_data_entry', DATA_ENTRY_TABLE); | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data about data entries to sync. | ||||
|  */ | ||||
| export type AddonModDataEntryDBRecord = { | ||||
|     dataid: number; // Primary key.
 | ||||
|     entryid: number; // Primary key. Negative for offline entries.
 | ||||
|     action: AddonModDataAction; // Primary key.
 | ||||
|     courseid: number; | ||||
|     groupid: number; | ||||
|     fields: string; | ||||
|     timemodified: number; | ||||
| }; | ||||
							
								
								
									
										63
									
								
								src/addons/mod/data/services/handlers/approve-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/addons/mod/data/services/handlers/approve-link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Params } from '@angular/router'; | ||||
| import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | ||||
| import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModData } from '../data'; | ||||
| import { AddonModDataHelper } from '../data-helper'; | ||||
| 
 | ||||
| /** | ||||
|  * 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({ providedIn: 'root' }) | ||||
| export class AddonModDataApproveLinkHandlerService extends CoreContentLinksHandlerBase { | ||||
| 
 | ||||
|     name = 'AddonModDataApproveLinkHandler'; | ||||
|     featureName = 'CoreCourseModuleDelegate_AddonModData'; | ||||
|     pattern = /\/mod\/data\/view\.php.*([?&](d|approve|disapprove)=\d+)/; | ||||
|     priority = 50; // Higher priority than the default link handler for view.php.
 | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getActions(siteIds: string[], url: string, params: Params, courseId?: number): CoreContentLinksAction[] { | ||||
|         return [{ | ||||
|             action: (siteId): void => { | ||||
|                 const dataId = parseInt(params.d, 10); | ||||
|                 const entryId = parseInt(params.approve, 10) || parseInt(params.disapprove, 10); | ||||
|                 const approve = parseInt(params.approve, 10) ? true : false; | ||||
| 
 | ||||
|                 AddonModDataHelper.approveOrDisapproveEntry(dataId, entryId, approve, courseId, siteId); | ||||
|             }, | ||||
|         }]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async isEnabled(siteId: string, url: string, params: Params): 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 AddonModData.isPluginEnabled(siteId); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataApproveLinkHandler = makeSingleton(AddonModDataApproveLinkHandlerService); | ||||
							
								
								
									
										78
									
								
								src/addons/mod/data/services/handlers/default-field.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/addons/mod/data/services/handlers/default-field.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,78 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { FileEntry } from '@ionic-native/file'; | ||||
| import { CoreWSExternalFile } from '@services/ws'; | ||||
| import { AddonModDataEntryField, AddonModDataSearchEntriesAdvancedFieldFormatted, AddonModDataSubfieldData } from '../data'; | ||||
| import { AddonModDataFieldHandler } from '../data-fields-delegate'; | ||||
| 
 | ||||
| /** | ||||
|  * Default handler used when a field plugin doesn't have a specific implementation. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataDefaultFieldHandler implements AddonModDataFieldHandler { | ||||
| 
 | ||||
|     name = 'AddonModDataDefaultFieldHandler'; | ||||
|     type = 'default'; | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getFieldSearchData(): AddonModDataSearchEntriesAdvancedFieldFormatted[] { | ||||
|         return []; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getFieldEditData(): AddonModDataSubfieldData[] { | ||||
|         return []; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     hasFieldDataChanged(): boolean { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getFieldEditFiles(): (CoreWSExternalFile | FileEntry)[] { | ||||
|         return []; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getFieldsNotifications(): undefined { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     overrideData(originalContent: AddonModDataEntryField): AddonModDataEntryField { | ||||
|         return originalContent; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async isEnabled(): Promise<boolean> { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										61
									
								
								src/addons/mod/data/services/handlers/delete-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/addons/mod/data/services/handlers/delete-link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Params } from '@angular/router'; | ||||
| import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | ||||
| import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModData } from '../data'; | ||||
| import { AddonModDataHelper } from '../data-helper'; | ||||
| 
 | ||||
| /** | ||||
|  * Content links handler for database delete entry. | ||||
|  * Match mod/data/view.php?d=6&delete=5 with a valid data id and entryid. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataDeleteLinkHandlerService extends CoreContentLinksHandlerBase { | ||||
| 
 | ||||
|     name = 'AddonModDataDeleteLinkHandler'; | ||||
|     featureName = 'CoreCourseModuleDelegate_AddonModData'; | ||||
|     pattern = /\/mod\/data\/view\.php.*([?&](d|delete)=\d+)/; | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getActions(siteIds: string[], url: string, params: Params, courseId?: number): CoreContentLinksAction[] { | ||||
|         return [{ | ||||
|             action: (siteId): void => { | ||||
|                 const dataId = parseInt(params.d, 10); | ||||
|                 const entryId = parseInt(params.delete, 10); | ||||
| 
 | ||||
|                 AddonModDataHelper.showDeleteEntryModal(dataId, entryId, courseId, siteId); | ||||
|             }, | ||||
|         }]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async isEnabled(siteId: string, url: string, params: Params): Promise<boolean> { | ||||
|         if (typeof params.d == 'undefined' || typeof params.delete == 'undefined') { | ||||
|             // Required fields not defined. Cannot treat the URL.
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return AddonModData.isPluginEnabled(siteId); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataDeleteLinkHandler = makeSingleton(AddonModDataDeleteLinkHandlerService); | ||||
							
								
								
									
										79
									
								
								src/addons/mod/data/services/handlers/edit-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/addons/mod/data/services/handlers/edit-link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Params } from '@angular/router'; | ||||
| import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | ||||
| import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | ||||
| import { CoreCourse } from '@features/course/services/course'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModData } from '../data'; | ||||
| import { AddonModDataModuleHandlerService } from './module'; | ||||
| 
 | ||||
| /** | ||||
|  * 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({ providedIn: 'root' }) | ||||
| export class AddonModDataEditLinkHandlerService extends CoreContentLinksHandlerBase { | ||||
| 
 | ||||
|     name = 'AddonModDataEditLinkHandler'; | ||||
|     featureName = 'CoreCourseModuleDelegate_AddonModData'; | ||||
|     pattern = /\/mod\/data\/edit\.php.*([?&](d|rid)=\d+)/; | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] { | ||||
|         return [{ | ||||
|             action: async (siteId): Promise<void> => { | ||||
|                 const modal = await CoreDomUtils.showModalLoading(); | ||||
|                 const dataId = parseInt(params.d, 10); | ||||
|                 const rId = params.rid || ''; | ||||
| 
 | ||||
|                 try { | ||||
|                     const module = await CoreCourse.getModuleBasicInfoByInstance(dataId, 'data', siteId); | ||||
|                     const pageParams: Params = { | ||||
|                         module, | ||||
|                         courseId: module.course, | ||||
|                     }; | ||||
| 
 | ||||
|                     CoreNavigator.navigateToSitePath( | ||||
|                         `${AddonModDataModuleHandlerService.PAGE_NAME}/${module.course}/${module.id}/edit/${rId}`, | ||||
|                         { siteId, params: pageParams }, | ||||
|                     ); | ||||
|                 } finally { | ||||
|                     // Just in case. In fact we need to dismiss the modal before showing a toast or error message.
 | ||||
|                     modal.dismiss(); | ||||
|                 } | ||||
|             }, | ||||
|         }]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async isEnabled(siteId: string, url: string, params: Params): Promise<boolean> { | ||||
|         if (typeof params.d == 'undefined') { | ||||
|             // Id not defined. Cannot treat the URL.
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return AddonModData.isPluginEnabled(siteId); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataEditLinkHandler = makeSingleton(AddonModDataEditLinkHandlerService); | ||||
							
								
								
									
										40
									
								
								src/addons/mod/data/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/addons/mod/data/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModData } from '../data'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to treat links to data. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler { | ||||
| 
 | ||||
|     name = 'AddonModDataLinkHandler'; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super('AddonModData', 'data', 'd'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     isEnabled(siteId: string): Promise<boolean> { | ||||
|         return AddonModData.isPluginEnabled(siteId); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataIndexLinkHandler = makeSingleton(AddonModDataIndexLinkHandlerService); | ||||
							
								
								
									
										40
									
								
								src/addons/mod/data/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/addons/mod/data/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModData } from '../data'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to treat links to data list page. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataListLinkHandlerService extends CoreContentLinksModuleListHandler { | ||||
| 
 | ||||
|     name = 'AddonModDataListLinkHandler'; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super('AddonModData', 'data'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     isEnabled(siteId?: string): Promise<boolean> { | ||||
|         return AddonModData.isPluginEnabled(siteId); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataListLinkHandler = makeSingleton(AddonModDataListLinkHandlerService); | ||||
							
								
								
									
										85
									
								
								src/addons/mod/data/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/addons/mod/data/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { CoreConstants } from '@/core/constants'; | ||||
| import { Injectable, Type } from '@angular/core'; | ||||
| import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course'; | ||||
| import { CoreCourseModule } from '@features/course/services/course-helper'; | ||||
| import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; | ||||
| import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModDataIndexComponent } from '../../components/index'; | ||||
| import { AddonModData } from '../data'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to support data modules. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataModuleHandlerService implements CoreCourseModuleHandler { | ||||
| 
 | ||||
|     static readonly PAGE_NAME = 'mod_data'; | ||||
| 
 | ||||
|     name = 'AddonModData'; | ||||
|     modName = 'data'; | ||||
| 
 | ||||
|     supportedFeatures = { | ||||
|         [CoreConstants.FEATURE_GROUPS]: true, | ||||
|         [CoreConstants.FEATURE_GROUPINGS]: true, | ||||
|         [CoreConstants.FEATURE_MOD_INTRO]: true, | ||||
|         [CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true, | ||||
|         [CoreConstants.FEATURE_COMPLETION_HAS_RULES]: true, | ||||
|         [CoreConstants.FEATURE_GRADE_HAS_GRADE]: true, | ||||
|         [CoreConstants.FEATURE_GRADE_OUTCOMES]: true, | ||||
|         [CoreConstants.FEATURE_BACKUP_MOODLE2]: true, | ||||
|         [CoreConstants.FEATURE_SHOW_DESCRIPTION]: true, | ||||
|         [CoreConstants.FEATURE_RATE]: true, | ||||
|         [CoreConstants.FEATURE_COMMENT]: true, | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     isEnabled(): Promise<boolean> { | ||||
|         return AddonModData.isPluginEnabled(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getData(module: CoreCourseAnyModuleData): CoreCourseModuleHandlerData { | ||||
|         return { | ||||
|             icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined), | ||||
|             title: module.name, | ||||
|             class: 'addon-mod_data-handler', | ||||
|             showDownloadButton: true, | ||||
|             action(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void { | ||||
|                 options = options || {}; | ||||
|                 options.params = options.params || {}; | ||||
|                 Object.assign(options.params, { module }); | ||||
|                 const routeParams = '/' + courseId + '/' + module.id; | ||||
| 
 | ||||
|                 CoreNavigator.navigateToSitePath(AddonModDataModuleHandlerService.PAGE_NAME + routeParams, options); | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async getMainComponent(): Promise<Type<unknown>> { | ||||
|         return AddonModDataIndexComponent; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataModuleHandler = makeSingleton(AddonModDataModuleHandlerService); | ||||
							
								
								
									
										300
									
								
								src/addons/mod/data/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								src/addons/mod/data/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,300 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreComments } from '@features/comments/services/comments'; | ||||
| import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler'; | ||||
| import { CoreCourseCommonModWSOptions, CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course'; | ||||
| import { CoreFilepool } from '@services/filepool'; | ||||
| import { CoreGroup, CoreGroups } from '@services/groups'; | ||||
| import { CoreSitesCommonWSOptions, CoreSites, CoreSitesReadingStrategy } from '@services/sites'; | ||||
| import { CoreTimeUtils } from '@services/utils/time'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreWSExternalFile } from '@services/ws'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModDataProvider, AddonModDataEntry, AddonModData, AddonModDataData } from '../data'; | ||||
| import { AddonModDataSync, AddonModDataSyncResult } from '../data-sync'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to prefetch databases. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase { | ||||
| 
 | ||||
|     name = 'AddonModData'; | ||||
|     modName = 'data'; | ||||
|     component = AddonModDataProvider.COMPONENT; | ||||
|     updatesNames = /^configuration$|^.*files$|^entries$|^gradeitems$|^outcomes$|^comments$|^ratings/; | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieves all the entries for all the groups and then returns only unique entries. | ||||
|      * | ||||
|      * @param dataId Database Id. | ||||
|      * @param groups Array of groups in the activity. | ||||
|      * @param options Other options. | ||||
|      * @return All unique entries. | ||||
|      */ | ||||
|     protected async getAllUniqueEntries( | ||||
|         dataId: number, | ||||
|         groups: CoreGroup[], | ||||
|         options: CoreSitesCommonWSOptions = {}, | ||||
|     ): Promise<AddonModDataEntry[]> { | ||||
| 
 | ||||
|         const promises = groups.map((group) => AddonModData.fetchAllEntries(dataId, { | ||||
|             groupId: group.id, | ||||
|             ...options, // Include all options.
 | ||||
|         })); | ||||
| 
 | ||||
|         const responses = await Promise.all(promises); | ||||
| 
 | ||||
|         const uniqueEntries: Record<number, AddonModDataEntry> = {}; | ||||
|         responses.forEach((groupEntries) => { | ||||
|             groupEntries.forEach((entry) => { | ||||
|                 uniqueEntries[entry.id] = entry; | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         return CoreUtils.objectToArray(uniqueEntries); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Helper function to get all database info just once. | ||||
|      * | ||||
|      * @param module Module to get the files. | ||||
|      * @param courseId Course ID the module belongs to. | ||||
|      * @param omitFail True to always return even if fails. Default false. | ||||
|      * @param options Other options. | ||||
|      * @return Promise resolved with the info fetched. | ||||
|      */ | ||||
|     protected async getDatabaseInfoHelper( | ||||
|         module: CoreCourseAnyModuleData, | ||||
|         courseId: number, | ||||
|         omitFail: boolean, | ||||
|         options: CoreCourseCommonModWSOptions = {}, | ||||
|     ): Promise<{ database: AddonModDataData; groups: CoreGroup[]; entries: AddonModDataEntry[]; files: CoreWSExternalFile[]}> { | ||||
|         let groups: CoreGroup[] = []; | ||||
|         let entries: AddonModDataEntry[] = []; | ||||
|         let files: CoreWSExternalFile[] = []; | ||||
| 
 | ||||
|         options.cmId = options.cmId || module.id; | ||||
|         options.siteId = options.siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         const database = await AddonModData.getDatabase(courseId, module.id, options); | ||||
| 
 | ||||
|         try { | ||||
|             files = this.getIntroFilesFromInstance(module, database); | ||||
| 
 | ||||
|             const groupInfo = await CoreGroups.getActivityGroupInfo(module.id, false, undefined, options.siteId); | ||||
|             if (!groupInfo.groups || groupInfo.groups.length == 0) { | ||||
|                 groupInfo.groups = [{ id: 0, name: '' }]; | ||||
|             } | ||||
|             groups = groupInfo.groups || []; | ||||
| 
 | ||||
|             entries = await this.getAllUniqueEntries(database.id, groups, options); | ||||
|             files = files.concat(this.getEntriesFiles(entries)); | ||||
| 
 | ||||
|             return { | ||||
|                 database, | ||||
|                 groups, | ||||
|                 entries, | ||||
|                 files, | ||||
|             }; | ||||
|         } catch (error) { | ||||
|             if (omitFail) { | ||||
|                 // Any error, return the info we have.
 | ||||
|                 return { | ||||
|                     database, | ||||
|                     groups, | ||||
|                     entries, | ||||
|                     files, | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             throw error; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the file contained in the entries. | ||||
|      * | ||||
|      * @param entries List of entries to get files from. | ||||
|      * @return List of files. | ||||
|      */ | ||||
|     protected getEntriesFiles(entries: AddonModDataEntry[]): CoreWSExternalFile[] { | ||||
|         let files: CoreWSExternalFile[] = []; | ||||
| 
 | ||||
|         entries.forEach((entry) => { | ||||
|             CoreUtils.objectToArray(entry.contents).forEach((content) => { | ||||
|                 files = files.concat(<CoreWSExternalFile[]>content.files); | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         return files; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> { | ||||
|         return this.getDatabaseInfoHelper(module, courseId, true).then((info) => info.files); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> { | ||||
|         const data = await CoreUtils.ignoreErrors(AddonModData.getDatabase(courseId, module.id)); | ||||
| 
 | ||||
|         return this.getIntroFilesFromInstance(module, data); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async invalidateContent(moduleId: number, courseId: number): Promise<void> { | ||||
|         await AddonModData.invalidateContent(moduleId, courseId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async invalidateModule(module: CoreCourseAnyModuleData, courseId: number): Promise<void> { | ||||
|         const promises: Promise<void>[] = []; | ||||
|         promises.push(AddonModData.invalidateDatabaseData(courseId)); | ||||
|         promises.push(AddonModData.invalidateDatabaseAccessInformationData(module.instance!)); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async isDownloadable(module: CoreCourseAnyModuleData, courseId: number): Promise<boolean> { | ||||
|         const database = await AddonModData.getDatabase(courseId, module.id, { | ||||
|             readingStrategy: CoreSitesReadingStrategy.PreferCache, | ||||
|         }); | ||||
| 
 | ||||
|         const accessData = await AddonModData.getDatabaseAccessInformation(database.id, { cmId: module.id }); | ||||
|         // Check if database is restricted by time.
 | ||||
|         if (!accessData.timeavailable) { | ||||
|             const time = CoreTimeUtils.timestamp(); | ||||
| 
 | ||||
|             // It is restricted, checking times.
 | ||||
|             if (database.timeavailablefrom && time < database.timeavailablefrom) { | ||||
|                 return false; | ||||
|             } | ||||
|             if (database.timeavailableto && time > database.timeavailableto) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async isEnabled(): Promise<boolean> { | ||||
|         return AddonModData.isPluginEnabled(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     prefetch(module: CoreCourseAnyModuleData, courseId?: number): Promise<void> { | ||||
|         return this.prefetchPackage(module, courseId, this.prefetchDatabase.bind(this, module, courseId)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Prefetch a database. | ||||
|      * | ||||
|      * @param module Module. | ||||
|      * @param courseId Course ID the module belongs to. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async prefetchDatabase(module: CoreCourseAnyModuleData, courseId?: number): Promise<void> { | ||||
|         const siteId = CoreSites.getCurrentSiteId(); | ||||
|         courseId = courseId || module.course || CoreSites.getCurrentSiteHomeId(); | ||||
| 
 | ||||
|         const options = { | ||||
|             cmId: module.id, | ||||
|             readingStrategy: CoreSitesReadingStrategy.OnlyNetwork, | ||||
|             siteId, | ||||
|         }; | ||||
| 
 | ||||
|         const info = await this.getDatabaseInfoHelper(module, courseId, false, options); | ||||
| 
 | ||||
|         // Prefetch the database data.
 | ||||
|         const database = info.database; | ||||
| 
 | ||||
|         const commentsEnabled = !CoreComments.areCommentsDisabledInSite(); | ||||
| 
 | ||||
|         const promises: Promise<unknown>[] = []; | ||||
| 
 | ||||
|         promises.push(AddonModData.getFields(database.id, options)); | ||||
|         promises.push(CoreFilepool.addFilesToQueue(siteId, info.files, this.component, module.id)); | ||||
| 
 | ||||
|         info.groups.forEach((group) => { | ||||
|             promises.push(AddonModData.getDatabaseAccessInformation(database.id, { | ||||
|                 groupId: group.id, | ||||
|                 ...options, // Include all options.
 | ||||
|             })); | ||||
|         }); | ||||
| 
 | ||||
|         info.entries.forEach((entry) => { | ||||
|             promises.push(AddonModData.getEntry(database.id, entry.id, options)); | ||||
| 
 | ||||
|             if (commentsEnabled && database.comments) { | ||||
|                 promises.push(CoreComments.getComments( | ||||
|                     'module', | ||||
|                     database.coursemodule, | ||||
|                     'mod_data', | ||||
|                     entry.id, | ||||
|                     'database_entry', | ||||
|                     0, | ||||
|                     siteId, | ||||
|                 )); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         // Add Basic Info to manage links.
 | ||||
|         promises.push(CoreCourse.getModuleBasicInfoByInstance(database.id, 'data', siteId)); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sync a module. | ||||
|      * | ||||
|      * @param module Module. | ||||
|      * @param courseId Course ID the module belongs to | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async sync(module: CoreCourseAnyModuleData, courseId: number, siteId?: string): Promise<AddonModDataSyncResult> { | ||||
|         const promises = [ | ||||
|             AddonModDataSync.syncDatabase(module.instance!, siteId), | ||||
|             AddonModDataSync.syncRatings(module.id, true, siteId), | ||||
|         ]; | ||||
| 
 | ||||
|         const results = await Promise.all(promises); | ||||
| 
 | ||||
|         return results.reduce((a, b) => ({ | ||||
|             updated: a.updated || b.updated, | ||||
|             warnings: (a.warnings || []).concat(b.warnings || []), | ||||
|         }), { updated: false , warnings: [] }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataPrefetchHandler = makeSingleton(AddonModDataPrefetchHandlerService); | ||||
							
								
								
									
										94
									
								
								src/addons/mod/data/services/handlers/show-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/addons/mod/data/services/handlers/show-link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Params } from '@angular/router'; | ||||
| import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; | ||||
| import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; | ||||
| import { CoreCourse } from '@features/course/services/course'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModData } from '../data'; | ||||
| import { AddonModDataModuleHandlerService } from './module'; | ||||
| 
 | ||||
| /** | ||||
|  * Content links handler for database show entry. | ||||
|  * Match mod/data/view.php?d=6&rid=5 with a valid data id and entryid. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataShowLinkHandlerService extends CoreContentLinksHandlerBase { | ||||
| 
 | ||||
|     name = 'AddonModDataShowLinkHandler'; | ||||
|     featureName = 'CoreCourseModuleDelegate_AddonModData'; | ||||
|     pattern = /\/mod\/data\/view\.php.*([?&](d|rid|page|group|mode)=\d+)/; | ||||
|     priority = 50; // Higher priority than the default link handler for view.php.
 | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getActions(siteIds: string[], url: string, params: Params): CoreContentLinksAction[] { | ||||
|         return [{ | ||||
|             action: async (siteId): Promise<void> => { | ||||
|                 const modal = await CoreDomUtils.showModalLoading(); | ||||
|                 const dataId = parseInt(params.d, 10); | ||||
|                 const rId = params.rid || ''; | ||||
|                 const group = parseInt(params.group, 10) || false; | ||||
|                 const page = parseInt(params.page, 10) || false; | ||||
| 
 | ||||
|                 try { | ||||
|                     const module = await CoreCourse.getModuleBasicInfoByInstance(dataId, 'data', siteId); | ||||
|                     const pageParams: Params = { | ||||
|                         module: module, | ||||
|                         courseId: module.course, | ||||
|                     }; | ||||
| 
 | ||||
|                     if (group) { | ||||
|                         pageParams.group = group; | ||||
|                     } | ||||
| 
 | ||||
|                     if (params.mode && params.mode == 'single') { | ||||
|                         pageParams.offset = page || 0; | ||||
|                     } | ||||
| 
 | ||||
|                     CoreNavigator.navigateToSitePath( | ||||
|                         `${AddonModDataModuleHandlerService.PAGE_NAME}/${module.course}/${module.id}/${rId}`, | ||||
|                         { siteId, params: pageParams }, | ||||
|                     ); | ||||
|                 } finally { | ||||
|                     // Just in case. In fact we need to dismiss the modal before showing a toast or error message.
 | ||||
|                     modal.dismiss(); | ||||
|                 } | ||||
|             }, | ||||
|         }]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async isEnabled(siteId: string, url: string, params: Params): 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 AddonModData.isPluginEnabled(siteId); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataShowLinkHandler = makeSingleton(AddonModDataShowLinkHandlerService); | ||||
							
								
								
									
										43
									
								
								src/addons/mod/data/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/addons/mod/data/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreCronHandler } from '@services/cron'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModDataSync } from '../data-sync'; | ||||
| 
 | ||||
| /** | ||||
|  * Synchronization cron handler. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataSyncCronHandlerService implements CoreCronHandler { | ||||
| 
 | ||||
|     name = 'AddonModDataSyncCronHandler'; | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     execute(siteId?: string, force?: boolean): Promise<void> { | ||||
|         return AddonModDataSync.syncAllDatabases(siteId, force); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getInterval(): number { | ||||
|         return AddonModDataSync.syncInterval; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataSyncCronHandler = makeSingleton(AddonModDataSyncCronHandlerService); | ||||
							
								
								
									
										53
									
								
								src/addons/mod/data/services/handlers/tag-area.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/addons/mod/data/services/handlers/tag-area.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable, Type } from '@angular/core'; | ||||
| import { CoreTagFeedComponent } from '@features/tag/components/feed/feed'; | ||||
| import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate'; | ||||
| import { CoreTagFeedElement, CoreTagHelper } from '@features/tag/services/tag-helper'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModData } from '../data'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to support tags. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModDataTagAreaHandlerService implements CoreTagAreaHandler { | ||||
| 
 | ||||
|     name = 'AddonModDataTagAreaHandler'; | ||||
|     type = 'mod_data/data_records'; | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async isEnabled(): Promise<boolean> { | ||||
|         return AddonModData.isPluginEnabled(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     parseContent(content: string): CoreTagFeedElement[] { | ||||
|         return CoreTagHelper.parseFeedContent(content); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getComponent(): Type<unknown> { | ||||
|         return CoreTagFeedComponent; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| export const AddonModDataTagAreaHandler = makeSingleton(AddonModDataTagAreaHandlerService); | ||||
| @ -330,12 +330,12 @@ export class AddonModForumSyncProvider extends CoreCourseActivitySyncBaseProvide | ||||
|                 updated = true; | ||||
| 
 | ||||
|                 // Invalidate discussions of updated ratings.
 | ||||
|                 promises.push(AddonModForum.invalidateDiscussionPosts(result.itemSet!.itemSetId, undefined, siteId)); | ||||
|                 promises.push(AddonModForum.invalidateDiscussionPosts(result.itemSet.itemSetId, undefined, siteId)); | ||||
|             } | ||||
| 
 | ||||
|             if (result.warnings.length) { | ||||
|                 // Fetch forum to construct the warning message.
 | ||||
|                 promises.push(AddonModForum.getForum(result.itemSet!.courseId!, result.itemSet!.instanceId, { siteId }) | ||||
|                 promises.push(AddonModForum.getForum(result.itemSet.courseId, result.itemSet.instanceId, { siteId }) | ||||
|                     .then((forum) => { | ||||
|                         result.warnings.forEach((warning) => { | ||||
|                             this.addOfflineDataDeletedWarning(warnings, forum.name, warning); | ||||
|  | ||||
| @ -16,6 +16,7 @@ import { NgModule } from '@angular/core'; | ||||
| 
 | ||||
| import { AddonModAssignModule } from './assign/assign.module'; | ||||
| import { AddonModBookModule } from './book/book.module'; | ||||
| import { AddonModDataModule } from './data/data.module'; | ||||
| import { AddonModFolderModule } from './folder/folder.module'; | ||||
| import { AddonModForumModule } from './forum/forum.module'; | ||||
| import { AddonModLabelModule } from './label/label.module'; | ||||
| @ -32,10 +33,10 @@ import { AddonModScormModule } from './scorm/scorm.module'; | ||||
| import { AddonModChoiceModule } from './choice/choice.module'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [], | ||||
|     imports: [ | ||||
|         AddonModAssignModule, | ||||
|         AddonModBookModule, | ||||
|         AddonModDataModule, | ||||
|         AddonModForumModule, | ||||
|         AddonModLessonModule, | ||||
|         AddonModPageModule, | ||||
| @ -51,7 +52,5 @@ import { AddonModChoiceModule } from './choice/choice.module'; | ||||
|         AddonModScormModule, | ||||
|         AddonModChoiceModule, | ||||
|     ], | ||||
|     providers: [], | ||||
|     exports: [], | ||||
| }) | ||||
| export class AddonModModule { } | ||||
|  | ||||
| @ -126,6 +126,7 @@ import { ADDON_MOD_ASSIGN_SERVICES } from '@addons/mod/assign/assign.module'; | ||||
| import { ADDON_MOD_BOOK_SERVICES } from '@addons/mod/book/book.module'; | ||||
| // @todo import { ADDON_MOD_CHAT_SERVICES } from '@addons/mod/chat/chat.module';
 | ||||
| import { ADDON_MOD_CHOICE_SERVICES } from '@addons/mod/choice/choice.module'; | ||||
| import { ADDON_MOD_DATA_SERVICES } from '@addons/mod/data/data.module'; | ||||
| // @todo import { ADDON_MOD_FEEDBACK_SERVICES } from '@addons/mod/feedback/feedback.module';
 | ||||
| import { ADDON_MOD_FOLDER_SERVICES } from '@addons/mod/folder/folder.module'; | ||||
| import { ADDON_MOD_FORUM_SERVICES } from '@addons/mod/forum/forum.module'; | ||||
| @ -291,6 +292,7 @@ export class CoreCompileProvider { | ||||
|             ...ADDON_MOD_BOOK_SERVICES, | ||||
|             // @todo ...ADDON_MOD_CHAT_SERVICES,
 | ||||
|             ...ADDON_MOD_CHOICE_SERVICES, | ||||
|             ...ADDON_MOD_DATA_SERVICES, | ||||
|             // @todo ...ADDON_MOD_FEEDBACK_SERVICES,
 | ||||
|             ...ADDON_MOD_FOLDER_SERVICES, | ||||
|             ...ADDON_MOD_FORUM_SERVICES, | ||||
|  | ||||
| @ -92,7 +92,7 @@ export type CoreRatingDBPrimaryData = { | ||||
|  */ | ||||
| export type CoreRatingDBRecord = CoreRatingDBPrimaryData & { | ||||
|     itemsetid: number; | ||||
|     courseid?: number; | ||||
|     courseid: number; | ||||
|     scaleid: number; | ||||
|     rating: number; | ||||
|     rateduserid: number; | ||||
|  | ||||
| @ -28,7 +28,7 @@ export interface CoreRatingItemSet { | ||||
|     contextLevel: ContextLevel; | ||||
|     instanceId: number; | ||||
|     itemSetId: number; | ||||
|     courseId?: number; | ||||
|     courseId: number; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -59,12 +59,12 @@ export class CoreRatingSyncProvider extends CoreSyncBaseProvider<CoreRatingSyncI | ||||
|         itemSetId?: number, | ||||
|         force?: boolean, | ||||
|         siteId?: string, | ||||
|     ): Promise<CoreRatingSyncItem[]> { | ||||
|     ): Promise<CoreRatingSyncItemResult[]> { | ||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         const itemSets = await CoreRatingOffline.getItemSets(component, ratingArea, contextLevel, instanceId, itemSetId, siteId); | ||||
| 
 | ||||
|         const results: CoreRatingSyncItem[] = []; | ||||
|         const results: CoreRatingSyncItemResult[] = []; | ||||
|         await Promise.all(itemSets.map(async (itemSet) => { | ||||
|             const result = force | ||||
|                 ? await this.syncItemSet( | ||||
| @ -301,11 +301,14 @@ declare module '@singletons/events' { | ||||
| } | ||||
| 
 | ||||
| export type CoreRatingSyncItem = { | ||||
|     itemSet?: CoreRatingItemSet; | ||||
|     warnings: string[]; | ||||
|     updated: number[]; | ||||
| }; | ||||
| 
 | ||||
| export type CoreRatingSyncItemResult = CoreRatingSyncItem & { | ||||
|     itemSet: CoreRatingItemSet; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data passed to SYNCED_EVENT event. | ||||
|  */ | ||||
|  | ||||
| @ -2181,7 +2181,7 @@ export class CoreFilepoolProvider { | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     async invalidateFilesByComponent( | ||||
|         siteId: string, | ||||
|         siteId: string | undefined, | ||||
|         component: string, | ||||
|         componentId?: string | number, | ||||
|         onlyUnknown: boolean = true, | ||||
|  | ||||
| @ -1036,8 +1036,10 @@ export class CoreSitesProvider { | ||||
|      * @param siteId The site ID. If not defined, current site (if available). | ||||
|      * @return Promise resolved with the database. | ||||
|      */ | ||||
|     getSiteDb(siteId?: string): Promise<SQLiteDB> { | ||||
|         return this.getSite(siteId).then((site) => site.getDb()); | ||||
|     async getSiteDb(siteId?: string): Promise<SQLiteDB> { | ||||
|         const site = await this.getSite(siteId); | ||||
| 
 | ||||
|         return site.getDb(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -117,7 +117,7 @@ export class CoreUtilsProvider { | ||||
|      * @return The object. | ||||
|      */ | ||||
|     arrayToObject<T>( | ||||
|         array: T[], | ||||
|         array: T[] = [], | ||||
|         propertyName?: string, | ||||
|         result: Record<string, T> = {}, | ||||
|     ): Record<string, T> { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user