forked from CIT/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