diff --git a/src/addon/qtype/essay/providers/handler.ts b/src/addon/qtype/essay/providers/handler.ts index cee903848..d78f299ee 100644 --- a/src/addon/qtype/essay/providers/handler.ts +++ b/src/addon/qtype/essay/providers/handler.ts @@ -278,6 +278,15 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { // Store the files in the answers. answers[attachmentsInput.name + '_offline'] = JSON.stringify(result); } else { + // Check if any attachment was deleted. + const originalAttachments = this.questionHelper.getResponseFileAreaFiles(question, 'attachments'); + const filesToDelete = CoreFileUploader.instance.getFilesToDelete(originalAttachments, attachments); + + if (filesToDelete.length > 0) { + // Delete files. + await CoreFileUploader.instance.deleteDraftFiles(draftId, filesToDelete, siteId); + } + await CoreFileUploader.instance.uploadFiles(draftId, attachments, siteId); } } @@ -304,9 +313,17 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler { } if (answers && answers.attachments_offline) { - // Check if it has new attachments to upload. const attachmentsData = this.textUtils.parseJSON(answers.attachments_offline, {}); + // Check if any attachment was deleted. + const originalAttachments = this.questionHelper.getResponseFileAreaFiles(question, 'attachments'); + const filesToDelete = CoreFileUploader.instance.getFilesToDelete(originalAttachments, attachmentsData.online); + + if (filesToDelete.length > 0) { + // Delete files. + await CoreFileUploader.instance.deleteDraftFiles(answers.attachments, filesToDelete, siteId); + } + if (attachmentsData.offline) { // Upload the offline files. const offlineFiles = await this.questionHelper.getStoredQuestionFiles(question, component, componentId, siteId); diff --git a/src/core/fileuploader/providers/fileuploader.ts b/src/core/fileuploader/providers/fileuploader.ts index d27d41864..43bbc021b 100644 --- a/src/core/fileuploader/providers/fileuploader.ts +++ b/src/core/fileuploader/providers/fileuploader.ts @@ -29,6 +29,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreWSFileUploadOptions, CoreWSExternalFile } from '@providers/ws'; import { Subject } from 'rxjs'; import { CoreApp } from '@providers/app'; +import { CoreSite } from '@classes/site'; import { makeSingleton } from '@singletons/core.singletons'; /** @@ -106,6 +107,36 @@ export class CoreFileUploaderProvider { return false; } + /** + * Check if a certain site allows deleting draft files. + * + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if can delete. + * @since 3.10 + */ + async canDeleteDraftFiles(siteId?: string): Promise { + try { + const site = await this.sitesProvider.getSite(siteId); + + return this.canDeleteDraftFilesInSite(site); + } catch (error) { + return false; + } + } + + /** + * Check if a certain site allows deleting draft files. + * + * @param site Site. If not defined, use current site. + * @return Whether draft files can be deleted. + * @since 3.10 + */ + canDeleteDraftFilesInSite(site?: CoreSite): boolean { + site = site || this.sitesProvider.getCurrentSite(); + + return site.wsAvailable('core_files_delete_draft_files'); + } + /** * Start the audio recorder application and return information about captured audio clip files. * @@ -175,6 +206,25 @@ export class CoreFileUploaderProvider { }); } + /** + * Delete draft files. + * + * @param draftId Draft ID. + * @param files Files to delete. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + async deleteDraftFiles(draftId: number, files: {filepath: string, filename: string}[], siteId?: string): Promise { + const site = await this.sitesProvider.getSite(siteId); + + const params = { + draftitemid: draftId, + files: files, + }; + + return site.write('core_files_delete_draft_files', params); + } + /** * Get the upload options for a file taken with the Camera Cordova plugin. * @@ -217,6 +267,35 @@ export class CoreFileUploaderProvider { return options; } + /** + * Given a list of original files and a list of current files, return the list of files to delete. + * + * @param originalFiles Original files. + * @param currentFiles Current files. + * @return List of files to delete. + */ + getFilesToDelete(originalFiles: CoreWSExternalFile[], currentFiles: (CoreWSExternalFile | FileEntry)[]) + : {filepath: string, filename: string}[] { + + const filesToDelete: {filepath: string, filename: string}[] = []; + currentFiles = currentFiles || []; + + originalFiles.forEach((file) => { + const stillInList = currentFiles.some((currentFile) => { + return ( currentFile).fileurl == file.fileurl; + }); + + if (!stillInList) { + filesToDelete.push({ + filepath: file.filepath, + filename: file.filename, + }); + } + }); + + return filesToDelete; + } + /** * Get the upload options for a file of any type. *