forked from CIT/Vmeda.Online
		
	
						commit
						651cecebd7
					
				@ -24,7 +24,7 @@ import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/service
 | 
				
			|||||||
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
 | 
					import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
 | 
				
			||||||
import { CoreFilepool } from '@services/filepool';
 | 
					import { CoreFilepool } from '@services/filepool';
 | 
				
			||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonBlog } from '../blog';
 | 
					import { AddonBlog } from '../blog';
 | 
				
			||||||
import { AddonBlogMainMenuHandlerService } from './mainmenu';
 | 
					import { AddonBlogMainMenuHandlerService } from './mainmenu';
 | 
				
			||||||
@ -89,7 +89,7 @@ export class AddonBlogCourseOptionHandlerService implements CoreCourseOptionsHan
 | 
				
			|||||||
        const result = await AddonBlog.getEntries({ courseid: course.id });
 | 
					        const result = await AddonBlog.getEntries({ courseid: course.id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await Promise.all(result.entries.map(async (entry) => {
 | 
					        await Promise.all(result.entries.map(async (entry) => {
 | 
				
			||||||
            let files: CoreWSExternalFile[] = [];
 | 
					            let files: CoreWSFile[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (entry.attachmentfiles && entry.attachmentfiles.length) {
 | 
					            if (entry.attachmentfiles && entry.attachmentfiles.length) {
 | 
				
			||||||
                files = entry.attachmentfiles;
 | 
					                files = entry.attachmentfiles;
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@
 | 
				
			|||||||
import { Component, Input, OnInit, ViewChild, Type } from '@angular/core';
 | 
					import { Component, Input, OnInit, ViewChild, Type } from '@angular/core';
 | 
				
			||||||
import { CoreError } from '@classes/errors/error';
 | 
					import { CoreError } from '@classes/errors/error';
 | 
				
			||||||
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
 | 
					import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { ModalController } from '@singletons';
 | 
					import { ModalController } from '@singletons';
 | 
				
			||||||
import { AddonModAssignFeedbackCommentsTextData } from '../../feedback/comments/services/handler';
 | 
					import { AddonModAssignFeedbackCommentsTextData } from '../../feedback/comments/services/handler';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@ -53,7 +53,7 @@ export class AddonModAssignFeedbackPluginComponent implements OnInit {
 | 
				
			|||||||
    // Data to render the plugin if it isn't supported.
 | 
					    // Data to render the plugin if it isn't supported.
 | 
				
			||||||
    component = AddonModAssignProvider.COMPONENT;
 | 
					    component = AddonModAssignProvider.COMPONENT;
 | 
				
			||||||
    text = '';
 | 
					    text = '';
 | 
				
			||||||
    files: CoreWSExternalFile[] = [];
 | 
					    files: CoreWSFile[] = [];
 | 
				
			||||||
    notSupported = false;
 | 
					    notSupported = false;
 | 
				
			||||||
    pluginLoaded = false;
 | 
					    pluginLoaded = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { Component, Input, OnInit, Type, ViewChild } from '@angular/core';
 | 
					import { Component, Input, OnInit, Type, ViewChild } from '@angular/core';
 | 
				
			||||||
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
 | 
					import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    AddonModAssignAssign,
 | 
					    AddonModAssignAssign,
 | 
				
			||||||
    AddonModAssignSubmission,
 | 
					    AddonModAssignSubmission,
 | 
				
			||||||
@ -24,7 +23,7 @@ import {
 | 
				
			|||||||
} from '../../services/assign';
 | 
					} from '../../services/assign';
 | 
				
			||||||
import { AddonModAssignHelper, AddonModAssignPluginConfig } from '../../services/assign-helper';
 | 
					import { AddonModAssignHelper, AddonModAssignPluginConfig } from '../../services/assign-helper';
 | 
				
			||||||
import { AddonModAssignSubmissionDelegate } from '../../services/submission-delegate';
 | 
					import { AddonModAssignSubmissionDelegate } from '../../services/submission-delegate';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file/ngx';
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component that displays an assignment submission plugin.
 | 
					 * Component that displays an assignment submission plugin.
 | 
				
			||||||
@ -49,7 +48,7 @@ export class AddonModAssignSubmissionPluginComponent implements OnInit {
 | 
				
			|||||||
    // Data to render the plugin if it isn't supported.
 | 
					    // Data to render the plugin if it isn't supported.
 | 
				
			||||||
    component = AddonModAssignProvider.COMPONENT;
 | 
					    component = AddonModAssignProvider.COMPONENT;
 | 
				
			||||||
    text = '';
 | 
					    text = '';
 | 
				
			||||||
    files: (FileEntry | CoreWSExternalFile)[] = [];
 | 
					    files: CoreFileEntry[] = [];
 | 
				
			||||||
    notSupported = false;
 | 
					    notSupported = false;
 | 
				
			||||||
    pluginLoaded = false;
 | 
					    pluginLoaded = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@ import { Injectable, Type } from '@angular/core';
 | 
				
			|||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModAssignFeedbackCommentsComponent } from '../component/comments';
 | 
					import { AddonModAssignFeedbackCommentsComponent } from '../component/comments';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -126,7 +126,7 @@ export class AddonModAssignFeedbackCommentsHandlerService implements AddonModAss
 | 
				
			|||||||
        assign: AddonModAssignAssign,
 | 
					        assign: AddonModAssignAssign,
 | 
				
			||||||
        submission: AddonModAssignSubmission,
 | 
					        submission: AddonModAssignSubmission,
 | 
				
			||||||
        plugin: AddonModAssignPlugin,
 | 
					        plugin: AddonModAssignPlugin,
 | 
				
			||||||
    ): CoreWSExternalFile[] {
 | 
					    ): CoreWSFile[] {
 | 
				
			||||||
        return AddonModAssign.getSubmissionPluginAttachments(plugin);
 | 
					        return AddonModAssign.getSubmissionPluginAttachments(plugin);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@
 | 
				
			|||||||
import { AddonModAssignFeedbackPluginComponent } from '@addons/mod/assign/components/feedback-plugin/feedback-plugin';
 | 
					import { AddonModAssignFeedbackPluginComponent } from '@addons/mod/assign/components/feedback-plugin/feedback-plugin';
 | 
				
			||||||
import { AddonModAssignProvider, AddonModAssign } from '@addons/mod/assign/services/assign';
 | 
					import { AddonModAssignProvider, AddonModAssign } from '@addons/mod/assign/services/assign';
 | 
				
			||||||
import { Component, OnInit } from '@angular/core';
 | 
					import { Component, OnInit } from '@angular/core';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component to render a edit pdf feedback plugin.
 | 
					 * Component to render a edit pdf feedback plugin.
 | 
				
			||||||
@ -27,7 +27,7 @@ import { CoreWSExternalFile } from '@services/ws';
 | 
				
			|||||||
export class AddonModAssignFeedbackEditPdfComponent extends AddonModAssignFeedbackPluginComponent implements OnInit {
 | 
					export class AddonModAssignFeedbackEditPdfComponent extends AddonModAssignFeedbackPluginComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    component = AddonModAssignProvider.COMPONENT;
 | 
					    component = AddonModAssignProvider.COMPONENT;
 | 
				
			||||||
    files: CoreWSExternalFile[] = [];
 | 
					    files: CoreWSFile[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Component being initialized.
 | 
					     * Component being initialized.
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,7 @@ import {
 | 
				
			|||||||
} from '@addons/mod/assign/services/assign';
 | 
					} from '@addons/mod/assign/services/assign';
 | 
				
			||||||
import { AddonModAssignFeedbackHandler } from '@addons/mod/assign/services/feedback-delegate';
 | 
					import { AddonModAssignFeedbackHandler } from '@addons/mod/assign/services/feedback-delegate';
 | 
				
			||||||
import { Injectable, Type } from '@angular/core';
 | 
					import { Injectable, Type } from '@angular/core';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModAssignFeedbackEditPdfComponent } from '../component/editpdf';
 | 
					import { AddonModAssignFeedbackEditPdfComponent } from '../component/editpdf';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -56,7 +56,7 @@ export class AddonModAssignFeedbackEditPdfHandlerService implements AddonModAssi
 | 
				
			|||||||
        assign: AddonModAssignAssign,
 | 
					        assign: AddonModAssignAssign,
 | 
				
			||||||
        submission: AddonModAssignSubmission,
 | 
					        submission: AddonModAssignSubmission,
 | 
				
			||||||
        plugin: AddonModAssignPlugin,
 | 
					        plugin: AddonModAssignPlugin,
 | 
				
			||||||
    ): CoreWSExternalFile[] {
 | 
					    ): CoreWSFile[] {
 | 
				
			||||||
        return AddonModAssign.getSubmissionPluginAttachments(plugin);
 | 
					        return AddonModAssign.getSubmissionPluginAttachments(plugin);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@
 | 
				
			|||||||
import { AddonModAssignFeedbackPluginComponent } from '@addons/mod/assign/components/feedback-plugin/feedback-plugin';
 | 
					import { AddonModAssignFeedbackPluginComponent } from '@addons/mod/assign/components/feedback-plugin/feedback-plugin';
 | 
				
			||||||
import { AddonModAssign, AddonModAssignProvider } from '@addons/mod/assign/services/assign';
 | 
					import { AddonModAssign, AddonModAssignProvider } from '@addons/mod/assign/services/assign';
 | 
				
			||||||
import { Component, OnInit } from '@angular/core';
 | 
					import { Component, OnInit } from '@angular/core';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component to render a file feedback plugin.
 | 
					 * Component to render a file feedback plugin.
 | 
				
			||||||
@ -27,7 +27,7 @@ import { CoreWSExternalFile } from '@services/ws';
 | 
				
			|||||||
export class AddonModAssignFeedbackFileComponent extends AddonModAssignFeedbackPluginComponent implements OnInit {
 | 
					export class AddonModAssignFeedbackFileComponent extends AddonModAssignFeedbackPluginComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    component = AddonModAssignProvider.COMPONENT;
 | 
					    component = AddonModAssignProvider.COMPONENT;
 | 
				
			||||||
    files: CoreWSExternalFile[] = [];
 | 
					    files: CoreWSFile[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Component being initialized.
 | 
					     * Component being initialized.
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,7 @@ import {
 | 
				
			|||||||
} from '@addons/mod/assign/services/assign';
 | 
					} from '@addons/mod/assign/services/assign';
 | 
				
			||||||
import { AddonModAssignFeedbackHandler } from '@addons/mod/assign/services/feedback-delegate';
 | 
					import { AddonModAssignFeedbackHandler } from '@addons/mod/assign/services/feedback-delegate';
 | 
				
			||||||
import { Injectable, Type } from '@angular/core';
 | 
					import { Injectable, Type } from '@angular/core';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModAssignFeedbackFileComponent } from '../component/file';
 | 
					import { AddonModAssignFeedbackFileComponent } from '../component/file';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -56,7 +56,7 @@ export class AddonModAssignFeedbackFileHandlerService implements AddonModAssignF
 | 
				
			|||||||
        assign: AddonModAssignAssign,
 | 
					        assign: AddonModAssignAssign,
 | 
				
			||||||
        submission: AddonModAssignSubmission,
 | 
					        submission: AddonModAssignSubmission,
 | 
				
			||||||
        plugin: AddonModAssignPlugin,
 | 
					        plugin: AddonModAssignPlugin,
 | 
				
			||||||
    ): CoreWSExternalFile[] {
 | 
					    ): CoreWSFile[] {
 | 
				
			||||||
        return AddonModAssign.getSubmissionPluginAttachments(plugin);
 | 
					        return AddonModAssign.getSubmissionPluginAttachments(plugin);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,6 @@
 | 
				
			|||||||
import { Injectable } from '@angular/core';
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
					import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
				
			||||||
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
 | 
					import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
import { FileEntry } from '@ionic-native/file/ngx';
 | 
					import { FileEntry } from '@ionic-native/file/ngx';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    AddonModAssignProvider,
 | 
					    AddonModAssignProvider,
 | 
				
			||||||
@ -36,6 +35,7 @@ import { AddonModAssignSubmissionDelegate } from './submission-delegate';
 | 
				
			|||||||
import { AddonModAssignFeedbackDelegate } from './feedback-delegate';
 | 
					import { AddonModAssignFeedbackDelegate } from './feedback-delegate';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { CoreFormFields } from '@singletons/form';
 | 
					import { CoreFormFields } from '@singletons/form';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Service that provides some helper functions for assign.
 | 
					 * Service that provides some helper functions for assign.
 | 
				
			||||||
@ -642,7 +642,7 @@ export class AddonModAssignHelperProvider {
 | 
				
			|||||||
    async storeSubmissionFiles(
 | 
					    async storeSubmissionFiles(
 | 
				
			||||||
        assignId: number,
 | 
					        assignId: number,
 | 
				
			||||||
        folderName: string,
 | 
					        folderName: string,
 | 
				
			||||||
        files: (CoreWSExternalFile | FileEntry)[],
 | 
					        files: CoreFileEntry[],
 | 
				
			||||||
        userId?: number,
 | 
					        userId?: number,
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): Promise<CoreFileUploaderStoreFilesResult> {
 | 
					    ): Promise<CoreFileUploaderStoreFilesResult> {
 | 
				
			||||||
@ -661,7 +661,7 @@ export class AddonModAssignHelperProvider {
 | 
				
			|||||||
     * @param siteId Site ID. If not defined, current site.
 | 
					     * @param siteId Site ID. If not defined, current site.
 | 
				
			||||||
     * @return Promise resolved with the itemId.
 | 
					     * @return Promise resolved with the itemId.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    uploadFile(assignId: number, file: CoreWSExternalFile | FileEntry, itemId?: number, siteId?: string): Promise<number> {
 | 
					    uploadFile(assignId: number, file: CoreFileEntry, itemId?: number, siteId?: string): Promise<number> {
 | 
				
			||||||
        return CoreFileUploader.uploadOrReuploadFile(file, itemId, AddonModAssignProvider.COMPONENT, assignId, siteId);
 | 
					        return CoreFileUploader.uploadOrReuploadFile(file, itemId, AddonModAssignProvider.COMPONENT, assignId, siteId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -675,7 +675,7 @@ export class AddonModAssignHelperProvider {
 | 
				
			|||||||
     * @param siteId Site ID. If not defined, current site.
 | 
					     * @param siteId Site ID. If not defined, current site.
 | 
				
			||||||
     * @return Promise resolved with the itemId.
 | 
					     * @return Promise resolved with the itemId.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    uploadFiles(assignId: number, files: (CoreWSExternalFile | FileEntry)[], siteId?: string): Promise<number> {
 | 
					    uploadFiles(assignId: number, files: CoreFileEntry[], siteId?: string): Promise<number> {
 | 
				
			||||||
        return CoreFileUploader.uploadOrReuploadFiles(files, AddonModAssignProvider.COMPONENT, assignId, siteId);
 | 
					        return CoreFileUploader.uploadOrReuploadFiles(files, AddonModAssignProvider.COMPONENT, assignId, siteId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -693,7 +693,7 @@ export class AddonModAssignHelperProvider {
 | 
				
			|||||||
    async uploadOrStoreFiles(
 | 
					    async uploadOrStoreFiles(
 | 
				
			||||||
        assignId: number,
 | 
					        assignId: number,
 | 
				
			||||||
        folderName: string,
 | 
					        folderName: string,
 | 
				
			||||||
        files: (CoreWSExternalFile | FileEntry)[],
 | 
					        files: CoreFileEntry[],
 | 
				
			||||||
        offline = false,
 | 
					        offline = false,
 | 
				
			||||||
        userId?: number,
 | 
					        userId?: number,
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
 | 
				
			|||||||
@ -53,7 +53,7 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
				
			|||||||
    protected componentTranslatableString = 'assign';
 | 
					    protected componentTranslatableString = 'assign';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
        super('AddonModLessonSyncProvider');
 | 
					        super('AddonModAssignSyncProvider');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
 | 
				
			|||||||
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
					import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
				
			||||||
import { CoreInterceptor } from '@classes/interceptor';
 | 
					import { CoreInterceptor } from '@classes/interceptor';
 | 
				
			||||||
import { CoreWSExternalWarning, CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSExternalWarning, CoreWSExternalFile, CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
					import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
@ -405,12 +405,12 @@ export class AddonModAssignProvider {
 | 
				
			|||||||
     * @param submissionPlugin Submission plugin.
 | 
					     * @param submissionPlugin Submission plugin.
 | 
				
			||||||
     * @return Submission plugin attachments.
 | 
					     * @return Submission plugin attachments.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    getSubmissionPluginAttachments(submissionPlugin: AddonModAssignPlugin): CoreWSExternalFile[] {
 | 
					    getSubmissionPluginAttachments(submissionPlugin: AddonModAssignPlugin): CoreWSFile[] {
 | 
				
			||||||
        if (!submissionPlugin.fileareas) {
 | 
					        if (!submissionPlugin.fileareas) {
 | 
				
			||||||
            return [];
 | 
					            return [];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const files: CoreWSExternalFile[] = [];
 | 
					        const files: CoreWSFile[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        submissionPlugin.fileareas.forEach((filearea) => {
 | 
					        submissionPlugin.fileareas.forEach((filearea) => {
 | 
				
			||||||
            if (!filearea || !filearea.files) {
 | 
					            if (!filearea || !filearea.files) {
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
 | 
				
			|||||||
import { AddonModAssignDefaultFeedbackHandler } from './handlers/default-feedback';
 | 
					import { AddonModAssignDefaultFeedbackHandler } from './handlers/default-feedback';
 | 
				
			||||||
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin, AddonModAssignSavePluginData } from './assign';
 | 
					import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin, AddonModAssignSavePluginData } from './assign';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { AddonModAssignSubmissionFormatted } from './assign-helper';
 | 
					import { AddonModAssignSubmissionFormatted } from './assign-helper';
 | 
				
			||||||
import { CoreFormFields } from '@singletons/form';
 | 
					import { CoreFormFields } from '@singletons/form';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -79,7 +79,7 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
 | 
				
			|||||||
        submission: AddonModAssignSubmission,
 | 
					        submission: AddonModAssignSubmission,
 | 
				
			||||||
        plugin: AddonModAssignPlugin,
 | 
					        plugin: AddonModAssignPlugin,
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): CoreWSExternalFile[] | Promise<CoreWSExternalFile[]>;
 | 
					    ): CoreWSFile[] | Promise<CoreWSFile[]>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get a readable name to use for the plugin.
 | 
					     * Get a readable name to use for the plugin.
 | 
				
			||||||
@ -246,8 +246,8 @@ export class AddonModAssignFeedbackDelegateService extends CoreDelegate<AddonMod
 | 
				
			|||||||
        submission: AddonModAssignSubmission,
 | 
					        submission: AddonModAssignSubmission,
 | 
				
			||||||
        plugin: AddonModAssignPlugin,
 | 
					        plugin: AddonModAssignPlugin,
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): Promise<CoreWSExternalFile[]> {
 | 
					    ): Promise<CoreWSFile[]> {
 | 
				
			||||||
        const files: CoreWSExternalFile[] | undefined =
 | 
					        const files: CoreWSFile[] | undefined =
 | 
				
			||||||
            await this.executeFunctionOnEnabled(plugin.type, 'getPluginFiles', [assign, submission, plugin, siteId]);
 | 
					            await this.executeFunctionOnEnabled(plugin.type, 'getPluginFiles', [assign, submission, plugin, siteId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return files || [];
 | 
					        return files || [];
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@
 | 
				
			|||||||
// limitations under the License.
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Injectable } from '@angular/core';
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { Translate } from '@singletons';
 | 
					import { Translate } from '@singletons';
 | 
				
			||||||
import { AddonModAssignPlugin } from '../assign';
 | 
					import { AddonModAssignPlugin } from '../assign';
 | 
				
			||||||
import { AddonModAssignFeedbackHandler } from '../feedback-delegate';
 | 
					import { AddonModAssignFeedbackHandler } from '../feedback-delegate';
 | 
				
			||||||
@ -52,7 +52,7 @@ export class AddonModAssignDefaultFeedbackHandler implements AddonModAssignFeedb
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @return The files (or promise resolved with the files).
 | 
					     * @return The files (or promise resolved with the files).
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    getPluginFiles(): CoreWSExternalFile[] {
 | 
					    getPluginFiles(): CoreWSFile[] {
 | 
				
			||||||
        return [];
 | 
					        return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@
 | 
				
			|||||||
// limitations under the License.
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Injectable } from '@angular/core';
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { Translate } from '@singletons';
 | 
					import { Translate } from '@singletons';
 | 
				
			||||||
import { AddonModAssignPlugin } from '../assign';
 | 
					import { AddonModAssignPlugin } from '../assign';
 | 
				
			||||||
import { AddonModAssignSubmissionHandler } from '../submission-delegate';
 | 
					import { AddonModAssignSubmissionHandler } from '../submission-delegate';
 | 
				
			||||||
@ -79,7 +79,7 @@ export class AddonModAssignDefaultSubmissionHandler implements AddonModAssignSub
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @return The files (or promise resolved with the files).
 | 
					     * @return The files (or promise resolved with the files).
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    getPluginFiles(): CoreWSExternalFile[] {
 | 
					    getPluginFiles(): CoreWSFile[] {
 | 
				
			||||||
        return [];
 | 
					        return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,7 @@ import { AddonModAssignSubmissionDelegate } from '../submission-delegate';
 | 
				
			|||||||
import { AddonModAssignFeedbackDelegate } from '../feedback-delegate';
 | 
					import { AddonModAssignFeedbackDelegate } from '../feedback-delegate';
 | 
				
			||||||
import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
 | 
					import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
 | 
				
			||||||
import { CoreCourse, CoreCourseAnyModuleData, CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
					import { CoreCourse, CoreCourseAnyModuleData, CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { AddonModAssignHelper, AddonModAssignSubmissionFormatted } from '../assign-helper';
 | 
					import { AddonModAssignHelper, AddonModAssignSubmissionFormatted } from '../assign-helper';
 | 
				
			||||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
 | 
					import { CoreCourseHelper } from '@features/course/services/course-helper';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
@ -82,13 +82,13 @@ export class AddonModAssignPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
     * @param courseId Course ID the module belongs to.
 | 
					     * @param courseId Course ID the module belongs to.
 | 
				
			||||||
     * @return Promise resolved with the list of files.
 | 
					     * @return Promise resolved with the list of files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
					    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
        const siteId = CoreSites.getCurrentSiteId();
 | 
					        const siteId = CoreSites.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const assign = await AddonModAssign.getAssignment(courseId, module.id, { siteId });
 | 
					            const assign = await AddonModAssign.getAssignment(courseId, module.id, { siteId });
 | 
				
			||||||
            // Get intro files and attachments.
 | 
					            // Get intro files and attachments.
 | 
				
			||||||
            let files = assign.introattachments || [];
 | 
					            let files: CoreWSFile[] = assign.introattachments || [];
 | 
				
			||||||
            files = files.concat(this.getIntroFilesFromInstance(module, assign));
 | 
					            files = files.concat(this.getIntroFilesFromInstance(module, assign));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Now get the files in the submissions.
 | 
					            // Now get the files in the submissions.
 | 
				
			||||||
@ -145,7 +145,7 @@ export class AddonModAssignPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
        submitId: number,
 | 
					        submitId: number,
 | 
				
			||||||
        blindMarking: boolean,
 | 
					        blindMarking: boolean,
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): Promise<CoreWSExternalFile[]> {
 | 
					    ): Promise<CoreWSFile[]> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const submissionStatus = await AddonModAssign.getSubmissionStatusWithRetry(assign, {
 | 
					        const submissionStatus = await AddonModAssign.getSubmissionStatusWithRetry(assign, {
 | 
				
			||||||
            userId: submitId,
 | 
					            userId: submitId,
 | 
				
			||||||
@ -158,7 +158,7 @@ export class AddonModAssignPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
            return [];
 | 
					            return [];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const promises: Promise<CoreWSExternalFile[]>[] = [];
 | 
					        const promises: Promise<CoreWSFile[]>[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (userSubmission.plugins) {
 | 
					        if (userSubmission.plugins) {
 | 
				
			||||||
            // Add submission plugin files.
 | 
					            // Add submission plugin files.
 | 
				
			||||||
@ -245,7 +245,7 @@ export class AddonModAssignPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Get assignment to retrieve all its submissions.
 | 
					        // Get assignment to retrieve all its submissions.
 | 
				
			||||||
        const assign = await AddonModAssign.getAssignment(courseId, module.id, options);
 | 
					        const assign = await AddonModAssign.getAssignment(courseId, module.id, options);
 | 
				
			||||||
        const promises: Promise<any>[] = [];
 | 
					        const promises: Promise<unknown>[] = [];
 | 
				
			||||||
        const blindMarking = assign.blindmarking && !assign.revealidentities;
 | 
					        const blindMarking = assign.blindmarking && !assign.revealidentities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (blindMarking) {
 | 
					        if (blindMarking) {
 | 
				
			||||||
@ -259,7 +259,7 @@ export class AddonModAssignPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
        promises.push(CoreCourseHelper.getModuleCourseIdByInstance(assign.id, 'assign', siteId));
 | 
					        promises.push(CoreCourseHelper.getModuleCourseIdByInstance(assign.id, 'assign', siteId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Download intro files and attachments. Do not call getFiles because it'd call some WS twice.
 | 
					        // Download intro files and attachments. Do not call getFiles because it'd call some WS twice.
 | 
				
			||||||
        let files = assign.introattachments || [];
 | 
					        let files: CoreWSFile[] = assign.introattachments || [];
 | 
				
			||||||
        files = files.concat(this.getIntroFilesFromInstance(module, assign));
 | 
					        files = files.concat(this.getIntroFilesFromInstance(module, assign));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        promises.push(CoreFilepool.addFilesToQueue(siteId, files, this.component, module.id));
 | 
					        promises.push(CoreFilepool.addFilesToQueue(siteId, files, this.component, module.id));
 | 
				
			||||||
@ -293,7 +293,7 @@ export class AddonModAssignPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Get submissions.
 | 
					        // Get submissions.
 | 
				
			||||||
        const submissions = await AddonModAssign.getSubmissions(assign.id, modOptions);
 | 
					        const submissions = await AddonModAssign.getSubmissions(assign.id, modOptions);
 | 
				
			||||||
        const promises: Promise<any>[] = [];
 | 
					        const promises: Promise<unknown>[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        promises.push(this.prefetchParticipantSubmissions(
 | 
					        promises.push(this.prefetchParticipantSubmissions(
 | 
				
			||||||
            assign,
 | 
					            assign,
 | 
				
			||||||
@ -359,7 +359,7 @@ export class AddonModAssignPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
            AddonModAssignHelper.getSubmissionsUserData(assign, submissions, group.id, options)
 | 
					            AddonModAssignHelper.getSubmissionsUserData(assign, submissions, group.id, options)
 | 
				
			||||||
                .then((submissions: AddonModAssignSubmissionFormatted[]) => {
 | 
					                .then((submissions: AddonModAssignSubmissionFormatted[]) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    const subPromises: Promise<any>[] = submissions.map((submission) => {
 | 
					                    const subPromises: Promise<unknown>[] = submissions.map((submission) => {
 | 
				
			||||||
                        const submissionOptions = {
 | 
					                        const submissionOptions = {
 | 
				
			||||||
                            userId: submission.submitid,
 | 
					                            userId: submission.submitid,
 | 
				
			||||||
                            groupId: group.id,
 | 
					                            groupId: group.id,
 | 
				
			||||||
@ -426,7 +426,7 @@ export class AddonModAssignPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
        const userId = options.userId;
 | 
					        const userId = options.userId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const promises: Promise<any>[] = [];
 | 
					            const promises: Promise<unknown>[] = [];
 | 
				
			||||||
            const blindMarking = !!assign.blindmarking && !assign.revealidentities;
 | 
					            const blindMarking = !!assign.blindmarking && !assign.revealidentities;
 | 
				
			||||||
            let userIds: number[] = [];
 | 
					            let userIds: number[] = [];
 | 
				
			||||||
            const userSubmission = AddonModAssign.getSubmissionObjectFromAttempt(assign, submission.lastattempt);
 | 
					            const userSubmission = AddonModAssign.getSubmissionObjectFromAttempt(assign, submission.lastattempt);
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
 | 
				
			|||||||
import { AddonModAssignDefaultSubmissionHandler } from './handlers/default-submission';
 | 
					import { AddonModAssignDefaultSubmissionHandler } from './handlers/default-submission';
 | 
				
			||||||
import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin, AddonModAssignSavePluginData } from './assign';
 | 
					import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin, AddonModAssignSavePluginData } from './assign';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { AddonModAssignSubmissionsDBRecordFormatted } from './assign-offline';
 | 
					import { AddonModAssignSubmissionsDBRecordFormatted } from './assign-offline';
 | 
				
			||||||
import { CoreFormFields } from '@singletons/form';
 | 
					import { CoreFormFields } from '@singletons/form';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -139,7 +139,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler {
 | 
				
			|||||||
        submission: AddonModAssignSubmission,
 | 
					        submission: AddonModAssignSubmission,
 | 
				
			||||||
        plugin: AddonModAssignPlugin,
 | 
					        plugin: AddonModAssignPlugin,
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): CoreWSExternalFile[] | Promise<CoreWSExternalFile[]>;
 | 
					    ): CoreWSFile[] | Promise<CoreWSFile[]>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get a readable name to use for the plugin.
 | 
					     * Get a readable name to use for the plugin.
 | 
				
			||||||
@ -384,8 +384,8 @@ export class AddonModAssignSubmissionDelegateService extends CoreDelegate<AddonM
 | 
				
			|||||||
        submission: AddonModAssignSubmission,
 | 
					        submission: AddonModAssignSubmission,
 | 
				
			||||||
        plugin: AddonModAssignPlugin,
 | 
					        plugin: AddonModAssignPlugin,
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): Promise<CoreWSExternalFile[]> {
 | 
					    ): Promise<CoreWSFile[]> {
 | 
				
			||||||
        const files: CoreWSExternalFile[] | undefined =
 | 
					        const files: CoreWSFile[] | undefined =
 | 
				
			||||||
            await this.executeFunctionOnEnabled(plugin.type, 'getPluginFiles', [assign, submission, plugin, siteId]);
 | 
					            await this.executeFunctionOnEnabled(plugin.type, 'getPluginFiles', [assign, submission, plugin, siteId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return files || [];
 | 
					        return files || [];
 | 
				
			||||||
 | 
				
			|||||||
@ -24,10 +24,10 @@ import { AddonModAssignOffline, AddonModAssignSubmissionsDBRecordFormatted } fro
 | 
				
			|||||||
import { AddonModAssignSubmissionHandler } from '@addons/mod/assign/services/submission-delegate';
 | 
					import { AddonModAssignSubmissionHandler } from '@addons/mod/assign/services/submission-delegate';
 | 
				
			||||||
import { Injectable, Type } from '@angular/core';
 | 
					import { Injectable, Type } from '@angular/core';
 | 
				
			||||||
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
					import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
				
			||||||
import { CoreFileHelper } from '@services/file-helper';
 | 
					import { CoreFileEntry, CoreFileHelper } from '@services/file-helper';
 | 
				
			||||||
import { CoreFileSession } from '@services/file-session';
 | 
					import { CoreFileSession } from '@services/file-session';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModAssignSubmissionFileComponent } from '../component/file';
 | 
					import { AddonModAssignSubmissionFileComponent } from '../component/file';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file/ngx';
 | 
					import { FileEntry } from '@ionic-native/file/ngx';
 | 
				
			||||||
@ -156,7 +156,7 @@ export class AddonModAssignSubmissionFileHandlerService implements AddonModAssig
 | 
				
			|||||||
        assign: AddonModAssignAssign,
 | 
					        assign: AddonModAssignAssign,
 | 
				
			||||||
        submission: AddonModAssignSubmission,
 | 
					        submission: AddonModAssignSubmission,
 | 
				
			||||||
        plugin: AddonModAssignPlugin,
 | 
					        plugin: AddonModAssignPlugin,
 | 
				
			||||||
    ): CoreWSExternalFile[] {
 | 
					    ): CoreWSFile[] {
 | 
				
			||||||
        return AddonModAssign.getSubmissionPluginAttachments(plugin);
 | 
					        return AddonModAssign.getSubmissionPluginAttachments(plugin);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -350,14 +350,14 @@ export class AddonModAssignSubmissionFileHandlerService implements AddonModAssig
 | 
				
			|||||||
        submission: AddonModAssignSubmission,
 | 
					        submission: AddonModAssignSubmission,
 | 
				
			||||||
        offlineData?: AddonModAssignSubmissionsDBRecordFormatted,
 | 
					        offlineData?: AddonModAssignSubmissionsDBRecordFormatted,
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): Promise<(FileEntry | CoreWSExternalFile)[]> {
 | 
					    ): Promise<CoreFileEntry[]> {
 | 
				
			||||||
        const filesData = <CoreFileUploaderStoreFilesResult>offlineData?.plugindata.files_filemanager;
 | 
					        const filesData = <CoreFileUploaderStoreFilesResult>offlineData?.plugindata.files_filemanager;
 | 
				
			||||||
        if (!filesData) {
 | 
					        if (!filesData) {
 | 
				
			||||||
            return [];
 | 
					            return [];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Has some data to sync.
 | 
					        // Has some data to sync.
 | 
				
			||||||
        let files: (FileEntry | CoreWSExternalFile)[] = filesData.online || [];
 | 
					        let files: CoreFileEntry[] = filesData.online || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (filesData.offline) {
 | 
					        if (filesData.offline) {
 | 
				
			||||||
            // Has offline files, get them and add them to the list.
 | 
					            // Has offline files, get them and add them to the list.
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,7 @@ import { CoreFileHelper } from '@services/file-helper';
 | 
				
			|||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton, Translate } from '@singletons';
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
import { AddonModAssignSubmissionOnlineTextComponent } from '../component/onlinetext';
 | 
					import { AddonModAssignSubmissionOnlineTextComponent } from '../component/onlinetext';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -124,7 +124,7 @@ export class AddonModAssignSubmissionOnlineTextHandlerService implements AddonMo
 | 
				
			|||||||
        assign: AddonModAssignAssign,
 | 
					        assign: AddonModAssignAssign,
 | 
				
			||||||
        submission: AddonModAssignSubmission,
 | 
					        submission: AddonModAssignSubmission,
 | 
				
			||||||
        plugin: AddonModAssignPlugin,
 | 
					        plugin: AddonModAssignPlugin,
 | 
				
			||||||
    ): CoreWSExternalFile[] {
 | 
					    ): CoreWSFile[] {
 | 
				
			||||||
        return AddonModAssign.getSubmissionPluginAttachments(plugin);
 | 
					        return AddonModAssign.getSubmissionPluginAttachments(plugin);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
 | 
				
			|||||||
import { CoreCourseResourcePrefetchHandlerBase } from '@features/course/classes/resource-prefetch-handler';
 | 
					import { CoreCourseResourcePrefetchHandlerBase } from '@features/course/classes/resource-prefetch-handler';
 | 
				
			||||||
import { CoreCourseAnyModuleData, CoreCourseWSModule } from '@features/course/services/course';
 | 
					import { CoreCourseAnyModuleData, CoreCourseWSModule } from '@features/course/services/course';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModBook, AddonModBookProvider } from '../book';
 | 
					import { AddonModBook, AddonModBookProvider } from '../book';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -55,7 +55,7 @@ export class AddonModBookPrefetchHandlerService extends CoreCourseResourcePrefet
 | 
				
			|||||||
     * @param courseId Course ID.
 | 
					     * @param courseId Course ID.
 | 
				
			||||||
     * @return Promise resolved with list of intro files.
 | 
					     * @return Promise resolved with list of intro files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
					    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
        const book = await CoreUtils.ignoreErrors(AddonModBook.getBook(courseId, module.id));
 | 
					        const book = await CoreUtils.ignoreErrors(AddonModBook.getBook(courseId, module.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.getIntroFilesFromInstance(module, book);
 | 
					        return this.getIntroFilesFromInstance(module, book);
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ import { CoreUser } from '@features/user/services/user';
 | 
				
			|||||||
import { CoreFilepool } from '@services/filepool';
 | 
					import { CoreFilepool } from '@services/filepool';
 | 
				
			||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModChoice, AddonModChoiceProvider } from '../choice';
 | 
					import { AddonModChoice, AddonModChoiceProvider } from '../choice';
 | 
				
			||||||
import { AddonModChoiceSync, AddonModChoiceSyncResult } from '../choice-sync';
 | 
					import { AddonModChoiceSync, AddonModChoiceSyncResult } from '../choice-sync';
 | 
				
			||||||
@ -116,7 +116,7 @@ export class AddonModChoicePrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
					    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
        const choice = await CoreUtils.ignoreErrors(AddonModChoice.getChoice(courseId, module.id));
 | 
					        const choice = await CoreUtils.ignoreErrors(AddonModChoice.getChoice(courseId, module.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.getIntroFilesFromInstance(module, choice);
 | 
					        return this.getIntroFilesFromInstance(module, choice);
 | 
				
			||||||
 | 
				
			|||||||
@ -15,8 +15,7 @@ import { Component } from '@angular/core';
 | 
				
			|||||||
import { AddonModDataEntryField, AddonModDataProvider } from '@addons/mod/data/services/data';
 | 
					import { AddonModDataEntryField, AddonModDataProvider } from '@addons/mod/data/services/data';
 | 
				
			||||||
import { AddonModDataFieldPluginComponent } from '@addons/mod/data/classes/field-plugin-component';
 | 
					import { AddonModDataFieldPluginComponent } from '@addons/mod/data/classes/field-plugin-component';
 | 
				
			||||||
import { CoreFileSession } from '@services/file-session';
 | 
					import { CoreFileSession } from '@services/file-session';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component to render data file field.
 | 
					 * Component to render data file field.
 | 
				
			||||||
@ -27,7 +26,7 @@ import { FileEntry } from '@ionic-native/file';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class AddonModDataFieldFileComponent extends AddonModDataFieldPluginComponent {
 | 
					export class AddonModDataFieldFileComponent extends AddonModDataFieldPluginComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    files: (CoreWSExternalFile | FileEntry)[] = [];
 | 
					    files: CoreFileEntry[] = [];
 | 
				
			||||||
    component?: string;
 | 
					    component?: string;
 | 
				
			||||||
    componentId?: number;
 | 
					    componentId?: number;
 | 
				
			||||||
    maxSizeBytes?: number;
 | 
					    maxSizeBytes?: number;
 | 
				
			||||||
@ -38,7 +37,7 @@ export class AddonModDataFieldFileComponent extends AddonModDataFieldPluginCompo
 | 
				
			|||||||
     * @param value Input value.
 | 
					     * @param value Input value.
 | 
				
			||||||
     * @return List of files.
 | 
					     * @return List of files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected getFiles(value?: Partial<AddonModDataEntryField>): (CoreWSExternalFile | FileEntry)[] {
 | 
					    protected getFiles(value?: Partial<AddonModDataEntryField>): CoreFileEntry[] {
 | 
				
			||||||
        let files = value?.files || [];
 | 
					        let files = value?.files || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Reduce to first element.
 | 
					        // Reduce to first element.
 | 
				
			||||||
 | 
				
			|||||||
@ -22,12 +22,12 @@ import {
 | 
				
			|||||||
import { AddonModDataFieldHandler } from '@addons/mod/data/services/data-fields-delegate';
 | 
					import { AddonModDataFieldHandler } from '@addons/mod/data/services/data-fields-delegate';
 | 
				
			||||||
import { Injectable, Type } from '@angular/core';
 | 
					import { Injectable, Type } from '@angular/core';
 | 
				
			||||||
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
					import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file';
 | 
					import { FileEntry } from '@ionic-native/file/ngx';
 | 
				
			||||||
import { CoreFileSession } from '@services/file-session';
 | 
					import { CoreFileSession } from '@services/file-session';
 | 
				
			||||||
import { CoreFormFields } from '@singletons/form';
 | 
					import { CoreFormFields } from '@singletons/form';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
import { makeSingleton, Translate } from '@singletons';
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
import { AddonModDataFieldFileComponent } from '../component/file';
 | 
					import { AddonModDataFieldFileComponent } from '../component/file';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Handler for file data field plugin.
 | 
					 * Handler for file data field plugin.
 | 
				
			||||||
@ -77,7 +77,7 @@ export class AddonModDataFieldFileHandlerService implements AddonModDataFieldHan
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    getFieldEditFiles(field: AddonModDataField): (CoreWSExternalFile | FileEntry)[] {
 | 
					    getFieldEditFiles(field: AddonModDataField): CoreFileEntry[] {
 | 
				
			||||||
        return CoreFileSession.getFiles(AddonModDataProvider.COMPONENT,  field.dataid + '_' + field.id);
 | 
					        return CoreFileSession.getFiles(AddonModDataProvider.COMPONENT,  field.dataid + '_' + field.id);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -13,10 +13,9 @@
 | 
				
			|||||||
// limitations under the License.
 | 
					// limitations under the License.
 | 
				
			||||||
import { AddonModDataEntryField, AddonModDataProvider } from '@addons/mod/data/services/data';
 | 
					import { AddonModDataEntryField, AddonModDataProvider } from '@addons/mod/data/services/data';
 | 
				
			||||||
import { Component } from '@angular/core';
 | 
					import { Component } from '@angular/core';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file';
 | 
					import { CoreFileEntry, CoreFileHelper } from '@services/file-helper';
 | 
				
			||||||
import { CoreFileSession } from '@services/file-session';
 | 
					import { CoreFileSession } from '@services/file-session';
 | 
				
			||||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
 | 
					import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -28,12 +27,12 @@ import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class AddonModDataFieldPictureComponent extends AddonModDataFieldPluginComponent {
 | 
					export class AddonModDataFieldPictureComponent extends AddonModDataFieldPluginComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    files: (CoreWSExternalFile | FileEntry)[] = [];
 | 
					    files: CoreFileEntry[] = [];
 | 
				
			||||||
    component?: string;
 | 
					    component?: string;
 | 
				
			||||||
    componentId?: number;
 | 
					    componentId?: number;
 | 
				
			||||||
    maxSizeBytes?: number;
 | 
					    maxSizeBytes?: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    image?: CoreWSExternalFile | FileEntry;
 | 
					    image?: CoreFileEntry;
 | 
				
			||||||
    entryId?: number;
 | 
					    entryId?: number;
 | 
				
			||||||
    imageUrl?: string;
 | 
					    imageUrl?: string;
 | 
				
			||||||
    title?: string;
 | 
					    title?: string;
 | 
				
			||||||
@ -46,7 +45,7 @@ export class AddonModDataFieldPictureComponent extends AddonModDataFieldPluginCo
 | 
				
			|||||||
     * @param value Input value.
 | 
					     * @param value Input value.
 | 
				
			||||||
     * @return List of files.
 | 
					     * @return List of files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected getFiles(value?: Partial<AddonModDataEntryField>): (CoreWSExternalFile | FileEntry)[] {
 | 
					    protected getFiles(value?: Partial<AddonModDataEntryField>): CoreFileEntry[] {
 | 
				
			||||||
        let files = value?.files || [];
 | 
					        let files = value?.files || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Reduce to first element.
 | 
					        // Reduce to first element.
 | 
				
			||||||
@ -65,9 +64,9 @@ export class AddonModDataFieldPictureComponent extends AddonModDataFieldPluginCo
 | 
				
			|||||||
     * @return File found or false.
 | 
					     * @return File found or false.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected findFile(
 | 
					    protected findFile(
 | 
				
			||||||
        files: (CoreWSExternalFile | FileEntry)[],
 | 
					        files: CoreFileEntry[],
 | 
				
			||||||
        filenameSeek: string,
 | 
					        filenameSeek: string,
 | 
				
			||||||
    ): CoreWSExternalFile | FileEntry | undefined {
 | 
					    ): CoreFileEntry | undefined {
 | 
				
			||||||
        return files.find((file) => ('name' in file ? file.name : file.filename) == filenameSeek) || undefined;
 | 
					        return files.find((file) => ('name' in file ? file.name : file.filename) == filenameSeek) || undefined;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -130,7 +129,7 @@ export class AddonModDataFieldPictureComponent extends AddonModDataFieldPluginCo
 | 
				
			|||||||
                if (this.image) {
 | 
					                if (this.image) {
 | 
				
			||||||
                    this.imageUrl = 'name' in this.image
 | 
					                    this.imageUrl = 'name' in this.image
 | 
				
			||||||
                        ? this.image.toURL() // Is Offline.
 | 
					                        ? this.image.toURL() // Is Offline.
 | 
				
			||||||
                        : this.image.fileurl;
 | 
					                        : CoreFileHelper.getFileUrl(this.image);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }, 1);
 | 
					            }, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -22,12 +22,12 @@ import {
 | 
				
			|||||||
import { AddonModDataFieldHandler } from '@addons/mod/data/services/data-fields-delegate';
 | 
					import { AddonModDataFieldHandler } from '@addons/mod/data/services/data-fields-delegate';
 | 
				
			||||||
import { Injectable, Type } from '@angular/core';
 | 
					import { Injectable, Type } from '@angular/core';
 | 
				
			||||||
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
					import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file';
 | 
					import { FileEntry } from '@ionic-native/file/ngx';
 | 
				
			||||||
import { CoreFileSession } from '@services/file-session';
 | 
					import { CoreFileSession } from '@services/file-session';
 | 
				
			||||||
import { CoreFormFields } from '@singletons/form';
 | 
					import { CoreFormFields } from '@singletons/form';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
import { makeSingleton, Translate } from '@singletons';
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
import { AddonModDataFieldPictureComponent } from '../component/picture';
 | 
					import { AddonModDataFieldPictureComponent } from '../component/picture';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Handler for picture data field plugin.
 | 
					 * Handler for picture data field plugin.
 | 
				
			||||||
@ -88,7 +88,7 @@ export class AddonModDataFieldPictureHandlerService implements AddonModDataField
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    getFieldEditFiles(field: AddonModDataField): (CoreWSExternalFile | FileEntry)[] {
 | 
					    getFieldEditFiles(field: AddonModDataField): CoreFileEntry[] {
 | 
				
			||||||
        return CoreFileSession.getFiles(AddonModDataProvider.COMPONENT,  field.dataid + '_' + field.id);
 | 
					        return CoreFileSession.getFiles(AddonModDataProvider.COMPONENT,  field.dataid + '_' + field.id);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ import { Component } from '@angular/core';
 | 
				
			|||||||
import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
 | 
					import { AddonModDataFieldPluginComponent } from '../../../classes/field-plugin-component';
 | 
				
			||||||
import { AddonModDataEntryField, AddonModDataProvider } from '@addons/mod/data/services/data';
 | 
					import { AddonModDataEntryField, AddonModDataProvider } from '@addons/mod/data/services/data';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component to render data number field.
 | 
					 * Component to render data number field.
 | 
				
			||||||
@ -37,7 +37,7 @@ export class AddonModDataFieldTextareaComponent extends AddonModDataFieldPluginC
 | 
				
			|||||||
     * @return Replaced string to be rendered.
 | 
					     * @return Replaced string to be rendered.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    format(value?: Partial<AddonModDataEntryField>): string {
 | 
					    format(value?: Partial<AddonModDataEntryField>): string {
 | 
				
			||||||
        const files: CoreWSExternalFile[] = (value && <CoreWSExternalFile[]>value.files) || [];
 | 
					        const files: CoreWSFile[] = (value && <CoreWSFile[]>value.files) || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return value ? CoreTextUtils.replacePluginfileUrls(value.content || '', files) : '';
 | 
					        return value ? CoreTextUtils.replacePluginfileUrls(value.content || '', files) : '';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -14,13 +14,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { AddonModDataEntryField, AddonModDataField, AddonModDataSubfieldData } from '@addons/mod/data/services/data';
 | 
					import { AddonModDataEntryField, AddonModDataField, AddonModDataSubfieldData } from '@addons/mod/data/services/data';
 | 
				
			||||||
import { Injectable, Type } from '@angular/core';
 | 
					import { Injectable, Type } from '@angular/core';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file';
 | 
					 | 
				
			||||||
import { CoreFormFields } from '@singletons/form';
 | 
					import { CoreFormFields } from '@singletons/form';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton, Translate } from '@singletons';
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
import { AddonModDataFieldTextHandlerService } from '../../text/services/handler';
 | 
					import { AddonModDataFieldTextHandlerService } from '../../text/services/handler';
 | 
				
			||||||
import { AddonModDataFieldTextareaComponent } from '../component/textarea';
 | 
					import { AddonModDataFieldTextareaComponent } from '../component/textarea';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Handler for textarea data field plugin.
 | 
					 * Handler for textarea data field plugin.
 | 
				
			||||||
@ -49,7 +49,7 @@ export class AddonModDataFieldTextareaHandlerService extends AddonModDataFieldTe
 | 
				
			|||||||
        const fieldName = 'f_' + field.id;
 | 
					        const fieldName = 'f_' + field.id;
 | 
				
			||||||
        const files = this.getFieldEditFiles(field, inputData, originalFieldData);
 | 
					        const files = this.getFieldEditFiles(field, inputData, originalFieldData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let text = CoreTextUtils.restorePluginfileUrls(inputData[fieldName] || '', <CoreWSExternalFile[]>files);
 | 
					        let text = CoreTextUtils.restorePluginfileUrls(inputData[fieldName] || '', <CoreWSFile[]> files);
 | 
				
			||||||
        // Add some HTML to the text if needed.
 | 
					        // Add some HTML to the text if needed.
 | 
				
			||||||
        text = CoreTextUtils.formatHtmlLines(text);
 | 
					        text = CoreTextUtils.formatHtmlLines(text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -83,7 +83,7 @@ export class AddonModDataFieldTextareaHandlerService extends AddonModDataFieldTe
 | 
				
			|||||||
        field: AddonModDataField,
 | 
					        field: AddonModDataField,
 | 
				
			||||||
        inputData: CoreFormFields,
 | 
					        inputData: CoreFormFields,
 | 
				
			||||||
        originalFieldData: AddonModDataEntryField,
 | 
					        originalFieldData: AddonModDataEntryField,
 | 
				
			||||||
    ): (CoreWSExternalFile | FileEntry)[] {
 | 
					    ): CoreFileEntry[] {
 | 
				
			||||||
        return (originalFieldData && originalFieldData.files) || [];
 | 
					        return (originalFieldData && originalFieldData.files) || [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -116,7 +116,7 @@ export class AddonModDataFieldTextareaHandlerService extends AddonModDataFieldTe
 | 
				
			|||||||
            // Take the original files since we cannot edit them on the app.
 | 
					            // Take the original files since we cannot edit them on the app.
 | 
				
			||||||
            originalContent.content = CoreTextUtils.replacePluginfileUrls(
 | 
					            originalContent.content = CoreTextUtils.replacePluginfileUrls(
 | 
				
			||||||
                originalContent.content,
 | 
					                originalContent.content,
 | 
				
			||||||
                <CoreWSExternalFile[]>originalContent.files,
 | 
					                <CoreWSFile[]> originalContent.files,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -22,8 +22,8 @@ import { AddonModDataEntryField,
 | 
				
			|||||||
    AddonModDataSubfieldData,
 | 
					    AddonModDataSubfieldData,
 | 
				
			||||||
} from './data';
 | 
					} from './data';
 | 
				
			||||||
import { CoreFormFields } from '@singletons/form';
 | 
					import { CoreFormFields } from '@singletons/form';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { FileEntry } from '@ionic-native/file/ngx';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file';
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Interface that all fields handlers must implement.
 | 
					 * Interface that all fields handlers must implement.
 | 
				
			||||||
@ -94,7 +94,7 @@ export interface AddonModDataFieldHandler extends CoreDelegateHandler {
 | 
				
			|||||||
        field: AddonModDataField,
 | 
					        field: AddonModDataField,
 | 
				
			||||||
        inputData: CoreFormFields,
 | 
					        inputData: CoreFormFields,
 | 
				
			||||||
        originalFieldData: AddonModDataEntryField,
 | 
					        originalFieldData: AddonModDataEntryField,
 | 
				
			||||||
    ): (CoreWSExternalFile | FileEntry)[];
 | 
					    ): CoreFileEntry[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Check and get field requeriments.
 | 
					     * Check and get field requeriments.
 | 
				
			||||||
@ -183,7 +183,7 @@ export class AddonModDataFieldsDelegateService extends CoreDelegate<AddonModData
 | 
				
			|||||||
        field: AddonModDataField,
 | 
					        field: AddonModDataField,
 | 
				
			||||||
        inputData: CoreFormFields,
 | 
					        inputData: CoreFormFields,
 | 
				
			||||||
        originalFieldData: CoreFormFields,
 | 
					        originalFieldData: CoreFormFields,
 | 
				
			||||||
    ): (CoreWSExternalFile | FileEntry)[] {
 | 
					    ): CoreFileEntry[] {
 | 
				
			||||||
        return this.executeFunctionOnEnabled(field.type, 'getFieldEditFiles', [field, inputData, originalFieldData]) || [];
 | 
					        return this.executeFunctionOnEnabled(field.type, 'getFieldEditFiles', [field, inputData, originalFieldData]) || [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -17,13 +17,12 @@ import { Injectable } from '@angular/core';
 | 
				
			|||||||
import { CoreCourse } from '@features/course/services/course';
 | 
					import { CoreCourse } from '@features/course/services/course';
 | 
				
			||||||
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
					import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
				
			||||||
import { CoreRatingOffline } from '@features/rating/services/rating-offline';
 | 
					import { CoreRatingOffline } from '@features/rating/services/rating-offline';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file';
 | 
					import { FileEntry } from '@ionic-native/file/ngx';
 | 
				
			||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
import { CoreFormFields } from '@singletons/form';
 | 
					import { CoreFormFields } from '@singletons/form';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
import { makeSingleton, Translate } from '@singletons';
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
import { CoreEvents } from '@singletons/events';
 | 
					import { CoreEvents } from '@singletons/events';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@ -44,6 +43,7 @@ import {
 | 
				
			|||||||
} from './data';
 | 
					} from './data';
 | 
				
			||||||
import { AddonModDataFieldsDelegate } from './data-fields-delegate';
 | 
					import { AddonModDataFieldsDelegate } from './data-fields-delegate';
 | 
				
			||||||
import { AddonModDataOffline, AddonModDataOfflineAction } from './data-offline';
 | 
					import { AddonModDataOffline, AddonModDataOfflineAction } from './data-offline';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Service that provides helper functions for datas.
 | 
					 * Service that provides helper functions for datas.
 | 
				
			||||||
@ -605,7 +605,7 @@ export class AddonModDataHelperProvider {
 | 
				
			|||||||
        inputData: CoreFormFields,
 | 
					        inputData: CoreFormFields,
 | 
				
			||||||
        fields: AddonModDataField[],
 | 
					        fields: AddonModDataField[],
 | 
				
			||||||
        entryContents: AddonModDataEntryFields,
 | 
					        entryContents: AddonModDataEntryFields,
 | 
				
			||||||
    ): Promise<(CoreWSExternalFile | FileEntry)[]> {
 | 
					    ): Promise<CoreFileEntry[]> {
 | 
				
			||||||
        if (!inputData) {
 | 
					        if (!inputData) {
 | 
				
			||||||
            return [];
 | 
					            return [];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -745,7 +745,7 @@ export class AddonModDataHelperProvider {
 | 
				
			|||||||
        dataId: number,
 | 
					        dataId: number,
 | 
				
			||||||
        entryId: number,
 | 
					        entryId: number,
 | 
				
			||||||
        fieldId: number,
 | 
					        fieldId: number,
 | 
				
			||||||
        files: (CoreWSExternalFile | FileEntry)[],
 | 
					        files: CoreFileEntry[],
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): Promise<CoreFileUploaderStoreFilesResult> {
 | 
					    ): Promise<CoreFileUploaderStoreFilesResult> {
 | 
				
			||||||
        // Get the folder where to store the files.
 | 
					        // Get the folder where to store the files.
 | 
				
			||||||
@ -771,7 +771,7 @@ export class AddonModDataHelperProvider {
 | 
				
			|||||||
        itemId: number = 0,
 | 
					        itemId: number = 0,
 | 
				
			||||||
        entryId: number,
 | 
					        entryId: number,
 | 
				
			||||||
        fieldId: number,
 | 
					        fieldId: number,
 | 
				
			||||||
        files: (CoreWSExternalFile | FileEntry)[],
 | 
					        files: CoreFileEntry[],
 | 
				
			||||||
        offline: boolean,
 | 
					        offline: boolean,
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): Promise<number | CoreFileUploaderStoreFilesResult> {
 | 
					    ): Promise<number | CoreFileUploaderStoreFilesResult> {
 | 
				
			||||||
 | 
				
			|||||||
@ -21,13 +21,12 @@ import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
				
			|||||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
					import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
				
			||||||
import { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
					import { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
				
			||||||
import { CoreRatingSync } from '@features/rating/services/rating-sync';
 | 
					import { CoreRatingSync } from '@features/rating/services/rating-sync';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file';
 | 
					 | 
				
			||||||
import { CoreApp } from '@services/app';
 | 
					import { CoreApp } from '@services/app';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreSync } from '@services/sync';
 | 
					import { CoreSync } from '@services/sync';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
import { Translate, makeSingleton } from '@singletons';
 | 
					import { Translate, makeSingleton } from '@singletons';
 | 
				
			||||||
import { CoreEvents } from '@singletons/events';
 | 
					import { CoreEvents } from '@singletons/events';
 | 
				
			||||||
import { AddonModDataProvider, AddonModData, AddonModDataData, AddonModDataAction } from './data';
 | 
					import { AddonModDataProvider, AddonModData, AddonModDataData, AddonModDataAction } from './data';
 | 
				
			||||||
@ -348,7 +347,7 @@ export class AddonModDataSyncProvider extends CoreCourseActivitySyncBaseProvider
 | 
				
			|||||||
                    // Upload Files if asked.
 | 
					                    // Upload Files if asked.
 | 
				
			||||||
                    const value = CoreTextUtils.parseJSON<CoreFileUploaderStoreFilesResult>(field.value || '');
 | 
					                    const value = CoreTextUtils.parseJSON<CoreFileUploaderStoreFilesResult>(field.value || '');
 | 
				
			||||||
                    if (value.online || value.offline) {
 | 
					                    if (value.online || value.offline) {
 | 
				
			||||||
                        let files: (CoreWSExternalFile | FileEntry)[] = value.online || [];
 | 
					                        let files: CoreFileEntry[] = value.online || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        const offlineFiles = value.offline
 | 
					                        const offlineFiles = value.offline
 | 
				
			||||||
                            ? await AddonModDataHelper.getStoredFiles(editAction.dataid, entryId, field.fieldid)
 | 
					                            ? await AddonModDataHelper.getStoredFiles(editAction.dataid, entryId, field.fieldid)
 | 
				
			||||||
 | 
				
			|||||||
@ -19,8 +19,8 @@ import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
				
			|||||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
					import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
				
			||||||
import { CoreRatingInfo } from '@features/rating/services/rating';
 | 
					import { CoreRatingInfo } from '@features/rating/services/rating';
 | 
				
			||||||
import { CoreTagItem } from '@features/tag/services/tag';
 | 
					import { CoreTagItem } from '@features/tag/services/tag';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file';
 | 
					 | 
				
			||||||
import { CoreApp } from '@services/app';
 | 
					import { CoreApp } from '@services/app';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
import { CoreFilepool } from '@services/filepool';
 | 
					import { CoreFilepool } from '@services/filepool';
 | 
				
			||||||
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
@ -1151,7 +1151,7 @@ export type AddonModDataEntryField = {
 | 
				
			|||||||
    content2: string; // Contents.
 | 
					    content2: string; // Contents.
 | 
				
			||||||
    content3: string; // Contents.
 | 
					    content3: string; // Contents.
 | 
				
			||||||
    content4: string; // Contents.
 | 
					    content4: string; // Contents.
 | 
				
			||||||
    files: (CoreWSExternalFile | FileEntry)[];
 | 
					    files: CoreFileEntry[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -1180,7 +1180,7 @@ export type AddonModDataSubfieldData = {
 | 
				
			|||||||
    fieldid: number;
 | 
					    fieldid: number;
 | 
				
			||||||
    subfield?: string;
 | 
					    subfield?: string;
 | 
				
			||||||
    value?: unknown; // Value encoded in JSON.
 | 
					    value?: unknown; // Value encoded in JSON.
 | 
				
			||||||
    files?: (CoreWSExternalFile | FileEntry)[];
 | 
					    files?: CoreFileEntry[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 | 
				
			|||||||
@ -11,9 +11,9 @@
 | 
				
			|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
					// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
// See the License for the specific language governing permissions and
 | 
					// See the License for the specific language governing permissions and
 | 
				
			||||||
// limitations under the License.
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Injectable } from '@angular/core';
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file';
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
import { AddonModDataEntryField, AddonModDataSearchEntriesAdvancedFieldFormatted, AddonModDataSubfieldData } from '../data';
 | 
					import { AddonModDataEntryField, AddonModDataSearchEntriesAdvancedFieldFormatted, AddonModDataSubfieldData } from '../data';
 | 
				
			||||||
import { AddonModDataFieldHandler } from '../data-fields-delegate';
 | 
					import { AddonModDataFieldHandler } from '../data-fields-delegate';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -50,7 +50,7 @@ export class AddonModDataDefaultFieldHandler implements AddonModDataFieldHandler
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    getFieldEditFiles(): (CoreWSExternalFile | FileEntry)[] {
 | 
					    getFieldEditFiles(): CoreFileEntry[] {
 | 
				
			||||||
        return [];
 | 
					        return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@ import { CoreGroup, CoreGroups } from '@services/groups';
 | 
				
			|||||||
import { CoreSitesCommonWSOptions, CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSitesCommonWSOptions, CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
					import { CoreTimeUtils } from '@services/utils/time';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModDataProvider, AddonModDataEntry, AddonModData, AddonModDataData } from '../data';
 | 
					import { AddonModDataProvider, AddonModDataEntry, AddonModData, AddonModDataData } from '../data';
 | 
				
			||||||
import { AddonModDataSync, AddonModDataSyncResult } from '../data-sync';
 | 
					import { AddonModDataSync, AddonModDataSyncResult } from '../data-sync';
 | 
				
			||||||
@ -82,10 +82,10 @@ export class AddonModDataPrefetchHandlerService extends CoreCourseActivityPrefet
 | 
				
			|||||||
        courseId: number,
 | 
					        courseId: number,
 | 
				
			||||||
        omitFail: boolean,
 | 
					        omitFail: boolean,
 | 
				
			||||||
        options: CoreCourseCommonModWSOptions = {},
 | 
					        options: CoreCourseCommonModWSOptions = {},
 | 
				
			||||||
    ): Promise<{ database: AddonModDataData; groups: CoreGroup[]; entries: AddonModDataEntry[]; files: CoreWSExternalFile[]}> {
 | 
					    ): Promise<{ database: AddonModDataData; groups: CoreGroup[]; entries: AddonModDataEntry[]; files: CoreWSFile[]}> {
 | 
				
			||||||
        let groups: CoreGroup[] = [];
 | 
					        let groups: CoreGroup[] = [];
 | 
				
			||||||
        let entries: AddonModDataEntry[] = [];
 | 
					        let entries: AddonModDataEntry[] = [];
 | 
				
			||||||
        let files: CoreWSExternalFile[] = [];
 | 
					        let files: CoreWSFile[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        options.cmId = options.cmId || module.id;
 | 
					        options.cmId = options.cmId || module.id;
 | 
				
			||||||
        options.siteId = options.siteId || CoreSites.getCurrentSiteId();
 | 
					        options.siteId = options.siteId || CoreSites.getCurrentSiteId();
 | 
				
			||||||
@ -131,12 +131,12 @@ export class AddonModDataPrefetchHandlerService extends CoreCourseActivityPrefet
 | 
				
			|||||||
     * @param entries List of entries to get files from.
 | 
					     * @param entries List of entries to get files from.
 | 
				
			||||||
     * @return List of files.
 | 
					     * @return List of files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected getEntriesFiles(entries: AddonModDataEntry[]): CoreWSExternalFile[] {
 | 
					    protected getEntriesFiles(entries: AddonModDataEntry[]): CoreWSFile[] {
 | 
				
			||||||
        let files: CoreWSExternalFile[] = [];
 | 
					        let files: CoreWSFile[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        entries.forEach((entry) => {
 | 
					        entries.forEach((entry) => {
 | 
				
			||||||
            CoreUtils.objectToArray(entry.contents).forEach((content) => {
 | 
					            CoreUtils.objectToArray(entry.contents).forEach((content) => {
 | 
				
			||||||
                files = files.concat(<CoreWSExternalFile[]>content.files);
 | 
					                files = files.concat(<CoreWSFile[]>content.files);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -146,14 +146,14 @@ export class AddonModDataPrefetchHandlerService extends CoreCourseActivityPrefet
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
					    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
        return this.getDatabaseInfoHelper(module, courseId, true).then((info) => info.files);
 | 
					        return this.getDatabaseInfoHelper(module, courseId, true).then((info) => info.files);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
					    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
        const data = await CoreUtils.ignoreErrors(AddonModData.getDatabase(courseId, module.id));
 | 
					        const data = await CoreUtils.ignoreErrors(AddonModData.getDatabase(courseId, module.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.getIntroFilesFromInstance(module, data);
 | 
					        return this.getIntroFilesFromInstance(module, data);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										34
									
								
								src/addons/mod/feedback/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/addons/mod/feedback/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					// (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 { CoreSharedModule } from '@/core/shared.module';
 | 
				
			||||||
 | 
					import { NgModule } from '@angular/core';
 | 
				
			||||||
 | 
					import { CoreCourseComponentsModule } from '@features/course/components/components.module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackIndexComponent } from './index/index';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					    declarations: [
 | 
				
			||||||
 | 
					        AddonModFeedbackIndexComponent,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    imports: [
 | 
				
			||||||
 | 
					        CoreSharedModule,
 | 
				
			||||||
 | 
					        CoreCourseComponentsModule,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    providers: [
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    exports: [
 | 
				
			||||||
 | 
					        AddonModFeedbackIndexComponent,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackComponentsModule {}
 | 
				
			||||||
@ -0,0 +1,230 @@
 | 
				
			|||||||
 | 
					<!-- Buttons to add to the header. -->
 | 
				
			||||||
 | 
					<core-navbar-buttons slot="end">
 | 
				
			||||||
 | 
					    <core-context-menu>
 | 
				
			||||||
 | 
					        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
 | 
				
			||||||
 | 
					            [href]="externalUrl" iconAction="fas-external-link-alt">
 | 
				
			||||||
 | 
					        </core-context-menu-item>
 | 
				
			||||||
 | 
					        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
				
			||||||
 | 
					            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
				
			||||||
 | 
					        </core-context-menu-item>
 | 
				
			||||||
 | 
					        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}"
 | 
				
			||||||
 | 
					            iconAction="far-newspaper" (action)="gotoBlog()">
 | 
				
			||||||
 | 
					        </core-context-menu-item>
 | 
				
			||||||
 | 
					        <core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
 | 
				
			||||||
 | 
					            (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
 | 
				
			||||||
 | 
					        </core-context-menu-item>
 | 
				
			||||||
 | 
					        <core-context-menu-item *ngIf="loaded && hasOffline && isOnline"  [priority]="600" (action)="doRefresh(null, $event, true)"
 | 
				
			||||||
 | 
					            [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
 | 
				
			||||||
 | 
					        </core-context-menu-item>
 | 
				
			||||||
 | 
					        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
				
			||||||
 | 
					            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
				
			||||||
 | 
					        </core-context-menu-item>
 | 
				
			||||||
 | 
					        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
				
			||||||
 | 
					            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
				
			||||||
 | 
					        </core-context-menu-item>
 | 
				
			||||||
 | 
					    </core-context-menu>
 | 
				
			||||||
 | 
					</core-navbar-buttons>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Content. -->
 | 
				
			||||||
 | 
					<core-loading [hideUntil]="loaded" class="core-loading-center">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
 | 
				
			||||||
 | 
					        contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
 | 
				
			||||||
 | 
					    </core-course-module-description>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <core-tabs [hideUntil]="tabsReady" [selectedIndex]="firstSelectedTab">
 | 
				
			||||||
 | 
					        <core-tab [title]="'addon.mod_feedback.overview' | translate" id="overview" (ionSelect)="tabChanged('overview')">
 | 
				
			||||||
 | 
					            <ng-template>
 | 
				
			||||||
 | 
					                <ng-container *ngTemplateOutlet="tabOverview"></ng-container>
 | 
				
			||||||
 | 
					            </ng-template>
 | 
				
			||||||
 | 
					        </core-tab>
 | 
				
			||||||
 | 
					        <core-tab *ngIf="showAnalysis && access && access.canviewreports" id="analysis"
 | 
				
			||||||
 | 
					            [title]="'addon.mod_feedback.analysis' | translate" (ionSelect)="tabChanged('analysis')">
 | 
				
			||||||
 | 
					            <ng-template>
 | 
				
			||||||
 | 
					                <ng-container *ngTemplateOutlet="tabAnalysis"></ng-container>
 | 
				
			||||||
 | 
					            </ng-template>
 | 
				
			||||||
 | 
					        </core-tab>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <core-tab *ngIf="showAnalysis && access && !access.canviewreports" id="analysis"
 | 
				
			||||||
 | 
					            [title]="'addon.mod_feedback.completed_feedbacks' | translate" (ionSelect)="tabChanged('analysis')">
 | 
				
			||||||
 | 
					            <ng-template>
 | 
				
			||||||
 | 
					                <ng-container *ngTemplateOutlet="tabAnalysis"></ng-container>
 | 
				
			||||||
 | 
					            </ng-template>
 | 
				
			||||||
 | 
					        </core-tab>
 | 
				
			||||||
 | 
					    </core-tabs>
 | 
				
			||||||
 | 
					</core-loading>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ng-template #basicInfo>
 | 
				
			||||||
 | 
					    <ion-list *ngIf="access && access.canviewanalysis && !access.isempty">
 | 
				
			||||||
 | 
					        <ion-item class="ion-text-wrap" *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)">
 | 
				
			||||||
 | 
					            <ion-label id="addon-feedback-groupslabel">
 | 
				
			||||||
 | 
					                <ng-container *ngIf="groupInfo.separateGroups">{{'core.groupsseparate' | translate }}</ng-container>
 | 
				
			||||||
 | 
					                <ng-container *ngIf="groupInfo.visibleGroups">{{'core.groupsvisible' | translate }}</ng-container>
 | 
				
			||||||
 | 
					            </ion-label>
 | 
				
			||||||
 | 
					            <ion-select [(ngModel)]="group" (ionChange)="setGroup(group)" aria-labelledby="addon-feedback-groupslabel"
 | 
				
			||||||
 | 
					                interface="action-sheet">
 | 
				
			||||||
 | 
					                <ion-select-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">
 | 
				
			||||||
 | 
					                    {{groupOpt.name}}
 | 
				
			||||||
 | 
					                </ion-select-option>
 | 
				
			||||||
 | 
					            </ion-select>
 | 
				
			||||||
 | 
					        </ion-item>
 | 
				
			||||||
 | 
					        <ion-item class="ion-text-wrap" (click)="openRespondents()" [detail]="access.canviewreports && completedCount > 0">
 | 
				
			||||||
 | 
					            <ion-label>
 | 
				
			||||||
 | 
					                <h2>{{ 'addon.mod_feedback.completed_feedbacks' | translate }}</h2>
 | 
				
			||||||
 | 
					            </ion-label>
 | 
				
			||||||
 | 
					            <ion-badge slot="end">{{completedCount}}</ion-badge>
 | 
				
			||||||
 | 
					        </ion-item>
 | 
				
			||||||
 | 
					        <ion-item class="ion-text-wrap" *ngIf="!access.isanonymous && access.canviewreports" (click)="openNonRespondents()"
 | 
				
			||||||
 | 
					            detail="true" tappable="true">
 | 
				
			||||||
 | 
					            <ion-label>
 | 
				
			||||||
 | 
					                <h2>{{ 'addon.mod_feedback.show_nonrespondents' | translate }}</h2>
 | 
				
			||||||
 | 
					            </ion-label>
 | 
				
			||||||
 | 
					        </ion-item>
 | 
				
			||||||
 | 
					        <ion-item class="ion-text-wrap">
 | 
				
			||||||
 | 
					            <ion-label>
 | 
				
			||||||
 | 
					                <h2>{{ 'addon.mod_feedback.questions' | translate }}</h2>
 | 
				
			||||||
 | 
					            </ion-label>
 | 
				
			||||||
 | 
					            <ion-badge slot="end">{{itemsCount}}</ion-badge>
 | 
				
			||||||
 | 
					        </ion-item>
 | 
				
			||||||
 | 
					    </ion-list>
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Template to render the overview. -->
 | 
				
			||||||
 | 
					<ng-template #tabOverview>
 | 
				
			||||||
 | 
					    <core-loading [hideUntil]="tabsLoaded.overview">
 | 
				
			||||||
 | 
					        <ng-container *ngTemplateOutlet="basicInfo"></ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- Feedback done in offline but not synchronized -->
 | 
				
			||||||
 | 
					        <ion-card class="core-warning-card" *ngIf="hasOffline">
 | 
				
			||||||
 | 
					            <ion-item>
 | 
				
			||||||
 | 
					                <ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
 | 
				
			||||||
 | 
					                <ion-label>{{ 'core.hasdatatosync' | translate:{$a: moduleName} }}</ion-label>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					        </ion-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ion-card class="core-info-card" *ngIf="access && access.cancomplete && !access.isopen">
 | 
				
			||||||
 | 
					            <ion-item>
 | 
				
			||||||
 | 
					                <ion-icon name="fas-question-circle" slot="start"></ion-icon>
 | 
				
			||||||
 | 
					                <ion-label>{{ 'addon.mod_feedback.feedback_is_not_open' | translate }}</ion-label>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					        </ion-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ion-card class="core-success-card" *ngIf="access && access.cancomplete && access.isopen && !access.cansubmit">
 | 
				
			||||||
 | 
					            <ion-item>
 | 
				
			||||||
 | 
					                <ion-icon name="fas-check" slot="start"></ion-icon>
 | 
				
			||||||
 | 
					                <ion-label>{{ 'addon.mod_feedback.this_feedback_is_already_submitted' | translate }}</ion-label>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					        </ion-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ion-list *ngIf="access && (access.canedititems || access.canviewreports || !access.isempty)">
 | 
				
			||||||
 | 
					            <ion-item class="ion-text-wrap" *ngIf="access.canedititems && overview.timeopen">
 | 
				
			||||||
 | 
					                <ion-label>
 | 
				
			||||||
 | 
					                    <h2>{{ 'addon.mod_feedback.feedbackopen' | translate }}</h2>
 | 
				
			||||||
 | 
					                    <p>{{overview.openTimeReadable}}</p>
 | 
				
			||||||
 | 
					                </ion-label>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					            <ion-item class="ion-text-wrap" *ngIf="access.canedititems && overview.timeclose">
 | 
				
			||||||
 | 
					                <ion-label>
 | 
				
			||||||
 | 
					                    <h2>{{ 'addon.mod_feedback.feedbackclose' | translate }}</h2>
 | 
				
			||||||
 | 
					                    <p>{{overview.closeTimeReadable}}</p>
 | 
				
			||||||
 | 
					                </ion-label>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					            <ion-item class="ion-text-wrap" *ngIf="access.canedititems && feedback && feedback.page_after_submit">
 | 
				
			||||||
 | 
					                <ion-label>
 | 
				
			||||||
 | 
					                    <h2>{{ 'addon.mod_feedback.page_after_submit' | translate }}</h2>
 | 
				
			||||||
 | 
					                    <core-format-text [component]="component" [componentId]="componentId" [text]="feedback.page_after_submit"
 | 
				
			||||||
 | 
					                        contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
 | 
				
			||||||
 | 
					                    </core-format-text>
 | 
				
			||||||
 | 
					                </ion-label>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					            <ng-container *ngIf="!access.isempty">
 | 
				
			||||||
 | 
					                <ion-item class="ion-text-wrap">
 | 
				
			||||||
 | 
					                    <ion-label>
 | 
				
			||||||
 | 
					                        <h2>{{ 'addon.mod_feedback.mode' | translate }}</h2>
 | 
				
			||||||
 | 
					                        <p *ngIf="access.isanonymous">{{ 'addon.mod_feedback.anonymous' | translate }}</p>
 | 
				
			||||||
 | 
					                        <p *ngIf="!access.isanonymous">{{ 'addon.mod_feedback.non_anonymous' | translate }}</p>
 | 
				
			||||||
 | 
					                    </ion-label>
 | 
				
			||||||
 | 
					                </ion-item>
 | 
				
			||||||
 | 
					                <ion-grid>
 | 
				
			||||||
 | 
					                    <ion-row class="ion-align-items-center">
 | 
				
			||||||
 | 
					                        <ion-col>
 | 
				
			||||||
 | 
					                            <ion-button expand="block" fill="outline" (click)="gotoAnswerQuestions(true)" class="ion-text-wrap">
 | 
				
			||||||
 | 
					                                <ion-icon name="fas-search" slot="start"></ion-icon>
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.preview' | translate }}
 | 
				
			||||||
 | 
					                            </ion-button>
 | 
				
			||||||
 | 
					                        </ion-col>
 | 
				
			||||||
 | 
					                        <ion-col *ngIf="access.cancomplete && access.cansubmit && access.isopen">
 | 
				
			||||||
 | 
					                            <ion-button expand="block" (click)="gotoAnswerQuestions()" class="ion-text-wrap">
 | 
				
			||||||
 | 
					                                <ng-container *ngIf="!goPage">
 | 
				
			||||||
 | 
					                                    {{ 'addon.mod_feedback.complete_the_form' | translate }}
 | 
				
			||||||
 | 
					                                </ng-container>
 | 
				
			||||||
 | 
					                                <ng-container *ngIf="goPage">
 | 
				
			||||||
 | 
					                                    {{ 'addon.mod_feedback.continue_the_form' | translate }}
 | 
				
			||||||
 | 
					                                </ng-container>
 | 
				
			||||||
 | 
					                                <ion-icon name="fas-chevron-right" slot="end"></ion-icon>
 | 
				
			||||||
 | 
					                            </ion-button>
 | 
				
			||||||
 | 
					                        </ion-col>
 | 
				
			||||||
 | 
					                    </ion-row>
 | 
				
			||||||
 | 
					                </ion-grid>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            </ng-container>
 | 
				
			||||||
 | 
					        </ion-list>
 | 
				
			||||||
 | 
					    </core-loading>
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Template to render the analysis. -->
 | 
				
			||||||
 | 
					<ng-template #tabAnalysis>
 | 
				
			||||||
 | 
					    <core-loading [hideUntil]="tabsLoaded.analysis">
 | 
				
			||||||
 | 
					        <ng-container *ngTemplateOutlet="basicInfo"></ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ng-container *ngIf="access && (access.canedititems || !access.isempty)">
 | 
				
			||||||
 | 
					            <ion-card class="core-info-card" *ngIf="warning">
 | 
				
			||||||
 | 
					                <ion-item>
 | 
				
			||||||
 | 
					                    <ion-icon name="fas-question-circle" slot="start"></ion-icon>
 | 
				
			||||||
 | 
					                    <ion-label>{{ warning }}</ion-label>
 | 
				
			||||||
 | 
					                </ion-item>
 | 
				
			||||||
 | 
					            </ion-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <ion-list *ngIf="items && items.length">
 | 
				
			||||||
 | 
					                <ion-item class="ion-text-wrap core-analysis" *ngFor="let item of items">
 | 
				
			||||||
 | 
					                    <ion-label>
 | 
				
			||||||
 | 
					                        <h2>
 | 
				
			||||||
 | 
					                            {{item.num}}. <core-format-text [component]="component" [componentId]="componentId" [text]="item.name"
 | 
				
			||||||
 | 
					                            contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
 | 
				
			||||||
 | 
					                            </core-format-text>
 | 
				
			||||||
 | 
					                        </h2>
 | 
				
			||||||
 | 
					                        <p>
 | 
				
			||||||
 | 
					                            <core-format-text [component]="component" [componentId]="componentId" [text]="item.label"
 | 
				
			||||||
 | 
					                                contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
 | 
				
			||||||
 | 
					                            </core-format-text>
 | 
				
			||||||
 | 
					                        </p>
 | 
				
			||||||
 | 
					                        <ng-container [ngSwitch]="item.templateName">
 | 
				
			||||||
 | 
					                            <ng-container *ngSwitchCase="'numeric'">
 | 
				
			||||||
 | 
					                                <ul>
 | 
				
			||||||
 | 
					                                    <li *ngFor="let data of item.data">{{ data }}</li>
 | 
				
			||||||
 | 
					                                </ul>
 | 
				
			||||||
 | 
					                                <p>{{ 'addon.mod_feedback.average' | translate }}: {{item.average | number : '1.2-2'}}</p>
 | 
				
			||||||
 | 
					                            </ng-container>
 | 
				
			||||||
 | 
					                            <ng-container *ngSwitchCase="'list'">
 | 
				
			||||||
 | 
					                                <ul>
 | 
				
			||||||
 | 
					                                    <ng-container *ngFor="let data of item.data">
 | 
				
			||||||
 | 
					                                        <li *ngIf="data">{{ data }}</li>
 | 
				
			||||||
 | 
					                                    </ng-container>
 | 
				
			||||||
 | 
					                                </ul>
 | 
				
			||||||
 | 
					                            </ng-container>
 | 
				
			||||||
 | 
					                            <ng-container *ngSwitchCase="'chart'">
 | 
				
			||||||
 | 
					                                <core-chart [type]="item.chartType" [data]="item.chartData" [labels]="item.labels" height="300"
 | 
				
			||||||
 | 
					                                    contextLevel="module" [contextInstanceId]="module.id" [wsNotFiltered]="true"
 | 
				
			||||||
 | 
					                                    [courseId]="courseId">
 | 
				
			||||||
 | 
					                                </core-chart>
 | 
				
			||||||
 | 
					                                <p *ngIf="item.average">
 | 
				
			||||||
 | 
					                                    {{ 'addon.mod_feedback.average' | translate }}: {{item.average | number : '1.2-2'}}
 | 
				
			||||||
 | 
					                                </p>
 | 
				
			||||||
 | 
					                            </ng-container>
 | 
				
			||||||
 | 
					                        </ng-container>
 | 
				
			||||||
 | 
					                    </ion-label>
 | 
				
			||||||
 | 
					                </ion-item>
 | 
				
			||||||
 | 
					            </ion-list>
 | 
				
			||||||
 | 
					        </ng-container>
 | 
				
			||||||
 | 
					    </core-loading>
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
							
								
								
									
										503
									
								
								src/addons/mod/feedback/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										503
									
								
								src/addons/mod/feedback/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,503 @@
 | 
				
			|||||||
 | 
					// (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 { Component, Input, Optional, ViewChild, OnInit, OnDestroy } from '@angular/core';
 | 
				
			||||||
 | 
					import { CoreTabsComponent } from '@components/tabs/tabs';
 | 
				
			||||||
 | 
					import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component';
 | 
				
			||||||
 | 
					import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
 | 
				
			||||||
 | 
					import { IonContent } from '@ionic/angular';
 | 
				
			||||||
 | 
					import { CoreGroupInfo, CoreGroups } from '@services/groups';
 | 
				
			||||||
 | 
					import { CoreNavigator } from '@services/navigator';
 | 
				
			||||||
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
 | 
					import { CoreTimeUtils } from '@services/utils/time';
 | 
				
			||||||
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
 | 
					import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    AddonModFeedback,
 | 
				
			||||||
 | 
					    AddonModFeedbackGetFeedbackAccessInformationWSResponse,
 | 
				
			||||||
 | 
					    AddonModFeedbackProvider,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSFeedback,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSItem,
 | 
				
			||||||
 | 
					} from '../../services/feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackOffline } from '../../services/feedback-offline';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    AddonModFeedbackAutoSyncData,
 | 
				
			||||||
 | 
					    AddonModFeedbackSync,
 | 
				
			||||||
 | 
					    AddonModFeedbackSyncProvider,
 | 
				
			||||||
 | 
					    AddonModFeedbackSyncResult,
 | 
				
			||||||
 | 
					} from '../../services/feedback-sync';
 | 
				
			||||||
 | 
					import { AddonModFeedbackModuleHandlerService } from '../../services/handlers/module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackPrefetchHandler } from '../../services/handlers/prefetch';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Component that displays a feedback index page.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					    selector: 'addon-mod-feedback-index',
 | 
				
			||||||
 | 
					    templateUrl: 'addon-mod-feedback-index.html',
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ViewChild(CoreTabsComponent) tabsComponent?: CoreTabsComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Input() tab = 'overview';
 | 
				
			||||||
 | 
					    @Input() group = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    component = AddonModFeedbackProvider.COMPONENT;
 | 
				
			||||||
 | 
					    moduleName = 'feedback';
 | 
				
			||||||
 | 
					    feedback?: AddonModFeedbackWSFeedback;
 | 
				
			||||||
 | 
					    goPage?: number;
 | 
				
			||||||
 | 
					    items: AddonModFeedbackItem[] = [];
 | 
				
			||||||
 | 
					    warning?: string;
 | 
				
			||||||
 | 
					    showAnalysis = false;
 | 
				
			||||||
 | 
					    tabsReady = false;
 | 
				
			||||||
 | 
					    firstSelectedTab?: number;
 | 
				
			||||||
 | 
					    access?: AddonModFeedbackGetFeedbackAccessInformationWSResponse;
 | 
				
			||||||
 | 
					    completedCount = 0;
 | 
				
			||||||
 | 
					    itemsCount = 0;
 | 
				
			||||||
 | 
					    groupInfo?: CoreGroupInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    overview = {
 | 
				
			||||||
 | 
					        timeopen: 0,
 | 
				
			||||||
 | 
					        openTimeReadable: '',
 | 
				
			||||||
 | 
					        timeclose: 0,
 | 
				
			||||||
 | 
					        closeTimeReadable: '',
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tabsLoaded = {
 | 
				
			||||||
 | 
					        overview: false,
 | 
				
			||||||
 | 
					        analysis: false,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected submitObserver: CoreEventObserver;
 | 
				
			||||||
 | 
					    protected syncEventName = AddonModFeedbackSyncProvider.AUTO_SYNCED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        protected content?: IonContent,
 | 
				
			||||||
 | 
					        @Optional() courseContentsPage?: CoreCourseContentsPage,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        super('AddonModLessonIndexComponent', content, courseContentsPage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Listen for form submit events.
 | 
				
			||||||
 | 
					        this.submitObserver = CoreEvents.on(AddonModFeedbackProvider.FORM_SUBMITTED, async (data) => {
 | 
				
			||||||
 | 
					            if (!this.feedback || data.feedbackId != this.feedback.id) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.tabsLoaded.analysis = false;
 | 
				
			||||||
 | 
					            this.tabsLoaded.overview = false;
 | 
				
			||||||
 | 
					            this.loaded = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Prefetch data if needed.
 | 
				
			||||||
 | 
					            if (!data.offline && this.isPrefetched()) {
 | 
				
			||||||
 | 
					                await CoreUtils.ignoreErrors(AddonModFeedbackSync.prefetchAfterUpdate(
 | 
				
			||||||
 | 
					                    AddonModFeedbackPrefetchHandler.instance,
 | 
				
			||||||
 | 
					                    this.module,
 | 
				
			||||||
 | 
					                    this.courseId,
 | 
				
			||||||
 | 
					                ));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Load the right tab.
 | 
				
			||||||
 | 
					            if (data.tab != this.tab) {
 | 
				
			||||||
 | 
					                this.tabChanged(data.tab);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                this.loadContent(true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, this.siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async ngOnInit(): Promise<void> {
 | 
				
			||||||
 | 
					        super.ngOnInit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await this.loadContent(false, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.feedback) {
 | 
				
			||||||
 | 
					                CoreUtils.ignoreErrors(AddonModFeedback.logView(this.feedback.id, this.feedback.name));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            this.tabsReady = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async invalidateContent(): Promise<void> {
 | 
				
			||||||
 | 
					        const promises: Promise<void>[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        promises.push(AddonModFeedback.invalidateFeedbackData(this.courseId));
 | 
				
			||||||
 | 
					        if (this.feedback) {
 | 
				
			||||||
 | 
					            promises.push(AddonModFeedback.invalidateFeedbackAccessInformationData(this.feedback.id));
 | 
				
			||||||
 | 
					            promises.push(AddonModFeedback.invalidateAnalysisData(this.feedback.id));
 | 
				
			||||||
 | 
					            promises.push(CoreGroups.invalidateActivityAllowedGroups(this.feedback.coursemodule));
 | 
				
			||||||
 | 
					            promises.push(CoreGroups.invalidateActivityGroupMode(this.feedback.coursemodule));
 | 
				
			||||||
 | 
					            promises.push(AddonModFeedback.invalidateResumePageData(this.feedback.id));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.tabsLoaded.analysis = false;
 | 
				
			||||||
 | 
					        this.tabsLoaded.overview = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await Promise.all(promises);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected isRefreshSyncNeeded(syncEventData: AddonModFeedbackAutoSyncData): boolean {
 | 
				
			||||||
 | 
					        if (this.feedback && syncEventData.feedbackId == this.feedback.id) {
 | 
				
			||||||
 | 
					            // Refresh the data.
 | 
				
			||||||
 | 
					            this.content?.scrollToTop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            this.feedback = await AddonModFeedback.getFeedback(this.courseId, this.module.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.description = this.feedback.intro;
 | 
				
			||||||
 | 
					            this.dataRetrieved.emit(this.feedback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (sync) {
 | 
				
			||||||
 | 
					                // Try to synchronize the feedback.
 | 
				
			||||||
 | 
					                await this.syncActivity(showErrors);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check if there are answers stored in offline.
 | 
				
			||||||
 | 
					            this.access = await AddonModFeedback.getFeedbackAccessInformation(this.feedback.id, { cmId: this.module.id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.showAnalysis = (this.access.canviewreports || this.access.canviewanalysis) && !this.access.isempty;
 | 
				
			||||||
 | 
					            this.firstSelectedTab = 0;
 | 
				
			||||||
 | 
					            if (!this.showAnalysis) {
 | 
				
			||||||
 | 
					                this.tab = 'overview';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.tab == 'analysis') {
 | 
				
			||||||
 | 
					                this.firstSelectedTab = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return await this.fetchFeedbackAnalysisData();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await this.fetchFeedbackOverviewData();
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            // Now fill the context menu.
 | 
				
			||||||
 | 
					            this.fillContextMenu(refresh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.feedback) {
 | 
				
			||||||
 | 
					                // Check if there are responses stored in offline.
 | 
				
			||||||
 | 
					                this.hasOffline = await AddonModFeedbackOffline.hasFeedbackOfflineData(this.feedback.id);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.tabsReady) {
 | 
				
			||||||
 | 
					                // Make sure the right tab is selected.
 | 
				
			||||||
 | 
					                this.tabsComponent?.selectTab(this.tab || 'overview');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Convenience function to get feedback overview data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return Resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async fetchFeedbackOverviewData(): Promise<void> {
 | 
				
			||||||
 | 
					        const promises: Promise<void>[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.access!.cancomplete && this.access!.cansubmit && this.access!.isopen) {
 | 
				
			||||||
 | 
					            promises.push(AddonModFeedback.getResumePage(this.feedback!.id, { cmId: this.module.id }).then((goPage) => {
 | 
				
			||||||
 | 
					                this.goPage = goPage > 0 ? goPage : undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.access!.canedititems) {
 | 
				
			||||||
 | 
					            this.overview.timeopen = (this.feedback!.timeopen || 0) * 1000;
 | 
				
			||||||
 | 
					            this.overview.openTimeReadable = this.overview.timeopen ? CoreTimeUtils.userDate(this.overview.timeopen) : '';
 | 
				
			||||||
 | 
					            this.overview.timeclose = (this.feedback!.timeclose || 0) * 1000;
 | 
				
			||||||
 | 
					            this.overview.closeTimeReadable = this.overview.timeclose ? CoreTimeUtils.userDate(this.overview.timeclose) : '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.access!.canviewanalysis) {
 | 
				
			||||||
 | 
					            // Get groups (only for teachers).
 | 
				
			||||||
 | 
					            promises.push(this.fetchGroupInfo(this.module.id));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await Promise.all(promises);
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            this.tabsLoaded.overview = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Convenience function to get feedback analysis data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param accessData Retrieved access data.
 | 
				
			||||||
 | 
					     * @return Resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async fetchFeedbackAnalysisData(): Promise<void> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            if (this.access!.canviewanalysis) {
 | 
				
			||||||
 | 
					                // Get groups (only for teachers).
 | 
				
			||||||
 | 
					                await this.fetchGroupInfo(this.module.id);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                this.tabChanged('overview');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            this.tabsLoaded.analysis = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetch Group info data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param cmId Course module ID.
 | 
				
			||||||
 | 
					     * @return Resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async fetchGroupInfo(cmId: number): Promise<void> {
 | 
				
			||||||
 | 
					        this.groupInfo = await CoreGroups.getActivityGroupInfo(cmId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await this.setGroup(CoreGroups.validateGroupId(this.group, this.groupInfo));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Parse the analysis info to show the info correctly formatted.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param item Item to parse.
 | 
				
			||||||
 | 
					     * @return Parsed item.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected parseAnalysisInfo(item: AddonModFeedbackItem): AddonModFeedbackItem {
 | 
				
			||||||
 | 
					        switch (item.typ) {
 | 
				
			||||||
 | 
					            case 'numeric':
 | 
				
			||||||
 | 
					                item.average = item.data.reduce((prev, current) => prev + Number(current), 0) / item.data.length;
 | 
				
			||||||
 | 
					                item.templateName = 'numeric';
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case 'info':
 | 
				
			||||||
 | 
					                item.data = <string[]> item.data.map((dataItem) => {
 | 
				
			||||||
 | 
					                    const parsed = <Record<string, string>> CoreTextUtils.parseJSON(dataItem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return typeof parsed.show != 'undefined' ? parsed.show : false;
 | 
				
			||||||
 | 
					                }).filter((dataItem) => dataItem); // Filter false entries.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case 'textfield':
 | 
				
			||||||
 | 
					            case 'textarea':
 | 
				
			||||||
 | 
					                item.templateName = 'list';
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case 'multichoicerated':
 | 
				
			||||||
 | 
					            case 'multichoice': {
 | 
				
			||||||
 | 
					                const parsedData = <Record<string, string | number>[]> item.data.map((dataItem) => {
 | 
				
			||||||
 | 
					                    const parsed = <Record<string, string | number>> CoreTextUtils.parseJSON(dataItem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return typeof parsed.answertext != 'undefined' ? parsed : false;
 | 
				
			||||||
 | 
					                }).filter((dataItem) => dataItem); // Filter false entries.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Format labels.
 | 
				
			||||||
 | 
					                item.labels = parsedData.map((dataItem) => {
 | 
				
			||||||
 | 
					                    dataItem.quotient = (<number> dataItem.quotient * 100).toFixed(2);
 | 
				
			||||||
 | 
					                    let label = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (typeof dataItem.value != 'undefined') {
 | 
				
			||||||
 | 
					                        label = '(' + dataItem.value + ') ';
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    label += dataItem.answertext;
 | 
				
			||||||
 | 
					                    label += Number(dataItem.quotient) > 0 ? ' (' + dataItem.quotient + '%)' : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return label;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                item.chartData = parsedData.map((dataItem) => <number> dataItem.answercount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (item.typ == 'multichoicerated') {
 | 
				
			||||||
 | 
					                    item.average = parsedData.reduce((prev, current) => prev + Number(current.avg), 0.0);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const subtype = item.presentation.charAt(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const single = subtype != 'c';
 | 
				
			||||||
 | 
					                item.chartType = single ? 'doughnut' : 'bar';
 | 
				
			||||||
 | 
					                item.templateName = 'chart';
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return item;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Function to go to the questions form.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param preview Preview or edit the form.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    gotoAnswerQuestions(preview: boolean = false): void {
 | 
				
			||||||
 | 
					        CoreNavigator.navigateToSitePath(
 | 
				
			||||||
 | 
					            AddonModFeedbackModuleHandlerService.PAGE_NAME + `/${this.courseId}/${this.module.id}/form`,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                params: {
 | 
				
			||||||
 | 
					                    preview,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * User entered the page that contains the component.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ionViewDidEnter(): void {
 | 
				
			||||||
 | 
					        super.ionViewDidEnter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.tabsComponent?.ionViewDidEnter();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * User left the page that contains the component.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ionViewDidLeave(): void {
 | 
				
			||||||
 | 
					        super.ionViewDidLeave();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.tabsComponent?.ionViewDidLeave();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Open non respondents page.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    openNonRespondents(): void {
 | 
				
			||||||
 | 
					        CoreNavigator.navigateToSitePath(
 | 
				
			||||||
 | 
					            AddonModFeedbackModuleHandlerService.PAGE_NAME + `/${this.courseId}/${this.module.id}/nonrespondents`,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                params: {
 | 
				
			||||||
 | 
					                    group: this.group,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Open respondents page.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    openRespondents(): void {
 | 
				
			||||||
 | 
					        if (!this.access!.canviewreports || this.completedCount <= 0) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        CoreNavigator.navigateToSitePath(
 | 
				
			||||||
 | 
					            AddonModFeedbackModuleHandlerService.PAGE_NAME + `/${this.courseId}/${this.module.id}/respondents`,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                params: {
 | 
				
			||||||
 | 
					                    group: this.group,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Tab changed, fetch content again.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param tabName New tab name.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    tabChanged(tabName: string): void {
 | 
				
			||||||
 | 
					        this.tab = tabName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.tabsLoaded[this.tab]) {
 | 
				
			||||||
 | 
					            this.loadContent(false, false, true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Set group to see the analysis.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param groupId Group ID.
 | 
				
			||||||
 | 
					     * @return Resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async setGroup(groupId: number): Promise<void> {
 | 
				
			||||||
 | 
					        this.group = groupId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const analysis = await AddonModFeedback.getAnalysis(this.feedback!.id, { groupId, cmId: this.module.id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.completedCount = analysis.completedcount;
 | 
				
			||||||
 | 
					        this.itemsCount = analysis.itemscount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.tab == 'analysis') {
 | 
				
			||||||
 | 
					            let num = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.items = <AddonModFeedbackItem[]> analysis.itemsdata.map((itemData) => {
 | 
				
			||||||
 | 
					                const item: AddonModFeedbackItem = Object.assign(itemData.item, {
 | 
				
			||||||
 | 
					                    data: itemData.data,
 | 
				
			||||||
 | 
					                    num: num++,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Move data inside item.
 | 
				
			||||||
 | 
					                if (item.data && item.data.length) {
 | 
				
			||||||
 | 
					                    return this.parseAnalysisInfo(item);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }).filter((item) => item);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.warning = '';
 | 
				
			||||||
 | 
					            if (analysis.warnings?.length) {
 | 
				
			||||||
 | 
					                const warning = analysis.warnings.find((warning) => warning.warningcode == 'insufficientresponsesforthisgroup');
 | 
				
			||||||
 | 
					                this.warning = warning?.message;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected sync(): Promise<AddonModFeedbackSyncResult> {
 | 
				
			||||||
 | 
					        return AddonModFeedbackSync.syncFeedback(this.feedback!.id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected hasSyncSucceed(result: AddonModFeedbackSyncResult): boolean {
 | 
				
			||||||
 | 
					        return result.updated;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Component being destroyed.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ngOnDestroy(): void {
 | 
				
			||||||
 | 
					        super.ngOnDestroy();
 | 
				
			||||||
 | 
					        this.submitObserver?.off();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AddonModFeedbackItem = AddonModFeedbackWSItem & {
 | 
				
			||||||
 | 
					    data: string[];
 | 
				
			||||||
 | 
					    num: number;
 | 
				
			||||||
 | 
					    templateName?: string;
 | 
				
			||||||
 | 
					    average?: number;
 | 
				
			||||||
 | 
					    labels?: string[];
 | 
				
			||||||
 | 
					    chartData?: number[];
 | 
				
			||||||
 | 
					    chartType?: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										82
									
								
								src/addons/mod/feedback/feedback-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/addons/mod/feedback/feedback-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					// (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 { NgModule } from '@angular/core';
 | 
				
			||||||
 | 
					import { RouterModule, Routes } from '@angular/router';
 | 
				
			||||||
 | 
					import { CoreSharedModule } from '@/core/shared.module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackComponentsModule } from './components/components.module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackIndexPage } from './pages/index/index';
 | 
				
			||||||
 | 
					import { AddonModFeedbackRespondentsPage } from './pages/respondents/respondents';
 | 
				
			||||||
 | 
					import { conditionalRoutes } from '@/app/app-routing.module';
 | 
				
			||||||
 | 
					import { CoreScreen } from '@services/screen';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const commonRoutes: Routes = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: ':courseId/:cmId',
 | 
				
			||||||
 | 
					        component: AddonModFeedbackIndexPage,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: ':courseId/:cmId/form',
 | 
				
			||||||
 | 
					        loadChildren: () => import('./pages/form/form.module').then(m => m.AddonModFeedbackFormPageModule),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: ':courseId/:cmId/nonrespondents',
 | 
				
			||||||
 | 
					        loadChildren: () => import('./pages/nonrespondents/nonrespondents.module')
 | 
				
			||||||
 | 
					            .then(m => m.AddonModFeedbackNonRespondentsPageModule),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const mobileRoutes: Routes = [
 | 
				
			||||||
 | 
					    ...commonRoutes,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: ':courseId/:cmId/respondents',
 | 
				
			||||||
 | 
					        component: AddonModFeedbackRespondentsPage,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: ':courseId/:cmId/attempt/:attemptId',
 | 
				
			||||||
 | 
					        loadChildren: () => import('./pages/attempt/attempt.module').then(m => m.AddonModFeedbackAttemptPageModule),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const tabletRoutes: Routes = [
 | 
				
			||||||
 | 
					    ...commonRoutes,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: ':courseId/:cmId/respondents',
 | 
				
			||||||
 | 
					        component: AddonModFeedbackRespondentsPage,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                path: 'attempt/:attemptId',
 | 
				
			||||||
 | 
					                loadChildren: () => import('./pages/attempt/attempt.module').then(m => m.AddonModFeedbackAttemptPageModule),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const routes: Routes = [
 | 
				
			||||||
 | 
					    ...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile),
 | 
				
			||||||
 | 
					    ...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					    imports: [
 | 
				
			||||||
 | 
					        RouterModule.forChild(routes),
 | 
				
			||||||
 | 
					        CoreSharedModule,
 | 
				
			||||||
 | 
					        AddonModFeedbackComponentsModule,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    declarations: [
 | 
				
			||||||
 | 
					        AddonModFeedbackIndexPage,
 | 
				
			||||||
 | 
					        AddonModFeedbackRespondentsPage,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackLazyModule {}
 | 
				
			||||||
							
								
								
									
										87
									
								
								src/addons/mod/feedback/feedback.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/addons/mod/feedback/feedback.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,87 @@
 | 
				
			|||||||
 | 
					// (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 { APP_INITIALIZER, NgModule, Type } from '@angular/core';
 | 
				
			||||||
 | 
					import { Routes } from '@angular/router';
 | 
				
			||||||
 | 
					import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
 | 
				
			||||||
 | 
					import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
 | 
				
			||||||
 | 
					import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
 | 
				
			||||||
 | 
					import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
				
			||||||
 | 
					import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate';
 | 
				
			||||||
 | 
					import { CoreCronDelegate } from '@services/cron';
 | 
				
			||||||
 | 
					import { CORE_SITE_SCHEMAS } from '@services/sites';
 | 
				
			||||||
 | 
					import { AddonModFeedbackComponentsModule } from './components/components.module';
 | 
				
			||||||
 | 
					import { OFFLINE_SITE_SCHEMA } from './services/database/feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackProvider } from './services/feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackHelperProvider } from './services/feedback-helper';
 | 
				
			||||||
 | 
					import { AddonModFeedbackOfflineProvider } from './services/feedback-offline';
 | 
				
			||||||
 | 
					import { AddonModFeedbackSyncProvider } from './services/feedback-sync';
 | 
				
			||||||
 | 
					import { AddonModFeedbackAnalysisLinkHandler } from './services/handlers/analysis-link';
 | 
				
			||||||
 | 
					import { AddonModFeedbackCompleteLinkHandler } from './services/handlers/complete-link';
 | 
				
			||||||
 | 
					import { AddonModFeedbackIndexLinkHandler } from './services/handlers/index-link';
 | 
				
			||||||
 | 
					import { AddonModFeedbackListLinkHandler } from './services/handlers/list-link';
 | 
				
			||||||
 | 
					import { AddonModFeedbackModuleHandlerService, AddonModFeedbackModuleHandler } from './services/handlers/module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackPrefetchHandler } from './services/handlers/prefetch';
 | 
				
			||||||
 | 
					import { AddonModFeedbackPrintLinkHandler } from './services/handlers/print-link';
 | 
				
			||||||
 | 
					import { AddonModFeedbackPushClickHandler } from './services/handlers/push-click';
 | 
				
			||||||
 | 
					import { AddonModFeedbackShowEntriesLinkHandler } from './services/handlers/show-entries-link';
 | 
				
			||||||
 | 
					import { AddonModFeedbackShowNonRespondentsLinkHandler } from './services/handlers/show-non-respondents-link';
 | 
				
			||||||
 | 
					import { AddonModFeedbackSyncCronHandler } from './services/handlers/sync-cron';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ADDON_MOD_FEEDBACK_SERVICES: Type<unknown>[] = [
 | 
				
			||||||
 | 
					    AddonModFeedbackProvider,
 | 
				
			||||||
 | 
					    AddonModFeedbackOfflineProvider,
 | 
				
			||||||
 | 
					    AddonModFeedbackHelperProvider,
 | 
				
			||||||
 | 
					    AddonModFeedbackSyncProvider,
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const routes: Routes = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: AddonModFeedbackModuleHandlerService.PAGE_NAME,
 | 
				
			||||||
 | 
					        loadChildren: () => import('./feedback-lazy.module').then(m => m.AddonModFeedbackLazyModule),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					    imports: [
 | 
				
			||||||
 | 
					        CoreMainMenuTabRoutingModule.forChild(routes),
 | 
				
			||||||
 | 
					        AddonModFeedbackComponentsModule,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    providers: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            provide: CORE_SITE_SCHEMAS,
 | 
				
			||||||
 | 
					            useValue: [OFFLINE_SITE_SCHEMA],
 | 
				
			||||||
 | 
					            multi: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            provide: APP_INITIALIZER,
 | 
				
			||||||
 | 
					            multi: true,
 | 
				
			||||||
 | 
					            deps: [],
 | 
				
			||||||
 | 
					            useFactory: () => () => {
 | 
				
			||||||
 | 
					                CoreCourseModuleDelegate.registerHandler(AddonModFeedbackModuleHandler.instance);
 | 
				
			||||||
 | 
					                CoreCourseModulePrefetchDelegate.registerHandler(AddonModFeedbackPrefetchHandler.instance);
 | 
				
			||||||
 | 
					                CoreCronDelegate.register(AddonModFeedbackSyncCronHandler.instance);
 | 
				
			||||||
 | 
					                CoreContentLinksDelegate.registerHandler(AddonModFeedbackIndexLinkHandler.instance);
 | 
				
			||||||
 | 
					                CoreContentLinksDelegate.registerHandler(AddonModFeedbackListLinkHandler.instance);
 | 
				
			||||||
 | 
					                CoreContentLinksDelegate.registerHandler(AddonModFeedbackAnalysisLinkHandler.instance);
 | 
				
			||||||
 | 
					                CoreContentLinksDelegate.registerHandler(AddonModFeedbackCompleteLinkHandler.instance);
 | 
				
			||||||
 | 
					                CoreContentLinksDelegate.registerHandler(AddonModFeedbackPrintLinkHandler.instance);
 | 
				
			||||||
 | 
					                CoreContentLinksDelegate.registerHandler(AddonModFeedbackShowEntriesLinkHandler.instance);
 | 
				
			||||||
 | 
					                CoreContentLinksDelegate.registerHandler(AddonModFeedbackShowNonRespondentsLinkHandler.instance);
 | 
				
			||||||
 | 
					                CorePushNotificationsDelegate.registerClickHandler(AddonModFeedbackPushClickHandler.instance);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackModule {}
 | 
				
			||||||
							
								
								
									
										38
									
								
								src/addons/mod/feedback/lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/addons/mod/feedback/lang.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "analysis": "Analysis",
 | 
				
			||||||
 | 
					    "anonymous": "Anonymous",
 | 
				
			||||||
 | 
					    "anonymous_entries": "Anonymous entries ({{$a}})",
 | 
				
			||||||
 | 
					    "average": "Average",
 | 
				
			||||||
 | 
					    "captchaofflinewarning": "Feedback with CAPTCHA cannot be completed offline, or if not configured, or if the server is down.",
 | 
				
			||||||
 | 
					    "complete_the_form": "Answer the questions",
 | 
				
			||||||
 | 
					    "completed_feedbacks": "Submitted answers",
 | 
				
			||||||
 | 
					    "continue_the_form": "Continue answering the questions",
 | 
				
			||||||
 | 
					    "feedback_is_not_open": "The feedback is not open",
 | 
				
			||||||
 | 
					    "feedback_submitted_offline": "This feedback has been saved to be submitted later.",
 | 
				
			||||||
 | 
					    "feedbackclose": "Allow answers to",
 | 
				
			||||||
 | 
					    "feedbackopen": "Allow answers from",
 | 
				
			||||||
 | 
					    "mapcourses": "Map feedback to courses",
 | 
				
			||||||
 | 
					    "maximal": "Maximum",
 | 
				
			||||||
 | 
					    "minimal": "Minimum",
 | 
				
			||||||
 | 
					    "mode": "Mode",
 | 
				
			||||||
 | 
					    "modulenameplural": "Feedback",
 | 
				
			||||||
 | 
					    "next_page": "Next page",
 | 
				
			||||||
 | 
					    "non_anonymous": "User's name will be logged and shown with answers",
 | 
				
			||||||
 | 
					    "non_anonymous_entries": "Non anonymous entries ({{$a}})",
 | 
				
			||||||
 | 
					    "non_respondents_students": "Non-respondent students ({{$a}})",
 | 
				
			||||||
 | 
					    "not_selected": "Not selected",
 | 
				
			||||||
 | 
					    "not_started": "Not started",
 | 
				
			||||||
 | 
					    "numberoutofrange": "Number out of range",
 | 
				
			||||||
 | 
					    "overview": "Overview",
 | 
				
			||||||
 | 
					    "page_after_submit": "Completion message",
 | 
				
			||||||
 | 
					    "preview": "Preview",
 | 
				
			||||||
 | 
					    "previous_page": "Previous page",
 | 
				
			||||||
 | 
					    "questions": "Questions",
 | 
				
			||||||
 | 
					    "response_nr": "Response number",
 | 
				
			||||||
 | 
					    "responses": "Responses",
 | 
				
			||||||
 | 
					    "save_entries": "Submit your answers",
 | 
				
			||||||
 | 
					    "show_entries": "Show responses",
 | 
				
			||||||
 | 
					    "show_nonrespondents": "Show non-respondents",
 | 
				
			||||||
 | 
					    "started": "Started",
 | 
				
			||||||
 | 
					    "this_feedback_is_already_submitted": "You've already completed this activity."
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								src/addons/mod/feedback/pages/attempt/attempt.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/addons/mod/feedback/pages/attempt/attempt.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					<ion-header>
 | 
				
			||||||
 | 
					    <ion-toolbar>
 | 
				
			||||||
 | 
					        <ion-buttons slot="start">
 | 
				
			||||||
 | 
					            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
				
			||||||
 | 
					        </ion-buttons>
 | 
				
			||||||
 | 
					        <ion-title>
 | 
				
			||||||
 | 
					            <ng-container *ngIf="attempt">{{ attempt.fullname }}</ng-container>
 | 
				
			||||||
 | 
					            <ng-container *ngIf="anonAttempt">
 | 
				
			||||||
 | 
					                {{ 'addon.mod_feedback.response_nr' |translate }}: {{anonAttempt.number}}
 | 
				
			||||||
 | 
					            </ng-container>
 | 
				
			||||||
 | 
					        </ion-title>
 | 
				
			||||||
 | 
					    </ion-toolbar>
 | 
				
			||||||
 | 
					</ion-header>
 | 
				
			||||||
 | 
					<ion-content>
 | 
				
			||||||
 | 
					    <core-loading [hideUntil]="loaded">
 | 
				
			||||||
 | 
					        <ion-list class="ion-no-margin" *ngIf="attempt || anonAttempt">
 | 
				
			||||||
 | 
					            <ion-item *ngIf="attempt" class="ion-text-wrap" core-user-link [userId]="attempt.userid"
 | 
				
			||||||
 | 
					                [attr.aria-label]=" 'core.user.viewprofile' | translate" [courseId]="attempt.courseid" [title]="attempt.fullname">
 | 
				
			||||||
 | 
					                <core-user-avatar [user]="attempt" slot="start"></core-user-avatar>
 | 
				
			||||||
 | 
					                <ion-label>
 | 
				
			||||||
 | 
					                    <h2>{{attempt.fullname}}</h2>
 | 
				
			||||||
 | 
					                    <p *ngIf="attempt.timemodified">{{attempt.timemodified * 1000 | coreFormatDate }}</p>
 | 
				
			||||||
 | 
					                </ion-label>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <ion-item class="ion-text-wrap" *ngIf="anonAttempt">
 | 
				
			||||||
 | 
					                <ion-label>
 | 
				
			||||||
 | 
					                    <h2>
 | 
				
			||||||
 | 
					                        {{ 'addon.mod_feedback.response_nr' |translate }}: {{anonAttempt.number}}
 | 
				
			||||||
 | 
					                        ({{ 'addon.mod_feedback.anonymous' |translate }})
 | 
				
			||||||
 | 
					                    </h2>
 | 
				
			||||||
 | 
					                </ion-label>
 | 
				
			||||||
 | 
					            </ion-item >
 | 
				
			||||||
 | 
					            <ng-container *ngIf="items && items.length">
 | 
				
			||||||
 | 
					                <ng-container *ngFor="let item of items">
 | 
				
			||||||
 | 
					                    <ion-item-divider *ngIf="item.typ == 'pagebreak'">
 | 
				
			||||||
 | 
					                        <ion-label></ion-label>
 | 
				
			||||||
 | 
					                    </ion-item-divider>
 | 
				
			||||||
 | 
					                    <ion-item class="ion-text-wrap" *ngIf="item.typ != 'pagebreak'" [color]="item.dependitem > 0 ? 'light' : ''">
 | 
				
			||||||
 | 
					                        <ion-label>
 | 
				
			||||||
 | 
					                            <h2 *ngIf="item.name" [core-mark-required]="item.required">
 | 
				
			||||||
 | 
					                                <span *ngIf="feedback!.autonumbering && item.itemnumber">{{item.itemnumber}}. </span>
 | 
				
			||||||
 | 
					                                <core-format-text  [component]="component" [componentId]="cmId" [text]="item.name"
 | 
				
			||||||
 | 
					                                    contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId">
 | 
				
			||||||
 | 
					                                </core-format-text>
 | 
				
			||||||
 | 
					                            </h2>
 | 
				
			||||||
 | 
					                            <p *ngIf="item.submittedValue">
 | 
				
			||||||
 | 
					                                <core-format-text [component]="component" [componentId]="cmId" [text]="item.submittedValue"
 | 
				
			||||||
 | 
					                                    contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId">
 | 
				
			||||||
 | 
					                                </core-format-text>
 | 
				
			||||||
 | 
					                            </p>
 | 
				
			||||||
 | 
					                        </ion-label>
 | 
				
			||||||
 | 
					                    </ion-item>
 | 
				
			||||||
 | 
					                </ng-container>
 | 
				
			||||||
 | 
					            </ng-container>
 | 
				
			||||||
 | 
					        </ion-list>
 | 
				
			||||||
 | 
					    </core-loading>
 | 
				
			||||||
 | 
					</ion-content>
 | 
				
			||||||
							
								
								
									
										37
									
								
								src/addons/mod/feedback/pages/attempt/attempt.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/addons/mod/feedback/pages/attempt/attempt.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					// (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 { CoreSharedModule } from '@/core/shared.module';
 | 
				
			||||||
 | 
					import { NgModule } from '@angular/core';
 | 
				
			||||||
 | 
					import { RouterModule, Routes } from '@angular/router';
 | 
				
			||||||
 | 
					import { AddonModFeedbackAttemptPage } from './attempt';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const routes: Routes = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: '',
 | 
				
			||||||
 | 
					        component: AddonModFeedbackAttemptPage,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					    declarations: [
 | 
				
			||||||
 | 
					        AddonModFeedbackAttemptPage,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    imports: [
 | 
				
			||||||
 | 
					        RouterModule.forChild(routes),
 | 
				
			||||||
 | 
					        CoreSharedModule,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    exports: [RouterModule],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackAttemptPageModule {}
 | 
				
			||||||
							
								
								
									
										125
									
								
								src/addons/mod/feedback/pages/attempt/attempt.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/addons/mod/feedback/pages/attempt/attempt.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,125 @@
 | 
				
			|||||||
 | 
					// (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 { Component, OnInit } from '@angular/core';
 | 
				
			||||||
 | 
					import { CoreNavigator } from '@services/navigator';
 | 
				
			||||||
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    AddonModFeedback,
 | 
				
			||||||
 | 
					    AddonModFeedbackProvider,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSAnonAttempt,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSAttempt,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSFeedback,
 | 
				
			||||||
 | 
					} from '../../services/feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackFormItem, AddonModFeedbackHelper } from '../../services/feedback-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Page that displays a feedback attempt review.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					    selector: 'page-addon-mod-feedback-attempt',
 | 
				
			||||||
 | 
					    templateUrl: 'attempt.html',
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackAttemptPage implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected attemptId!: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cmId!: number;
 | 
				
			||||||
 | 
					    courseId!: number;
 | 
				
			||||||
 | 
					    feedback?: AddonModFeedbackWSFeedback;
 | 
				
			||||||
 | 
					    attempt?: AddonModFeedbackWSAttempt;
 | 
				
			||||||
 | 
					    anonAttempt?: AddonModFeedbackWSAnonAttempt;
 | 
				
			||||||
 | 
					    items: AddonModFeedbackAttemptItem[] = [];
 | 
				
			||||||
 | 
					    component = AddonModFeedbackProvider.COMPONENT;
 | 
				
			||||||
 | 
					    loaded = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ngOnInit(): void {
 | 
				
			||||||
 | 
					        this.cmId = CoreNavigator.getRouteNumberParam('cmId')!;
 | 
				
			||||||
 | 
					        this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
 | 
				
			||||||
 | 
					        this.attemptId = CoreNavigator.getRouteNumberParam('attemptId')!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.fetchData();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetch all the data required for the view.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async fetchData(): Promise<void> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            this.feedback = await AddonModFeedback.getFeedback(this.courseId, this.cmId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const attempt = await AddonModFeedback.getAttempt(this.feedback.id, this.attemptId, { cmId: this.cmId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.isAnonAttempt(attempt)) {
 | 
				
			||||||
 | 
					                this.anonAttempt = attempt;
 | 
				
			||||||
 | 
					                delete this.attempt;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                this.attempt = attempt;
 | 
				
			||||||
 | 
					                delete this.anonAttempt;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const items = await AddonModFeedback.getItems(this.feedback.id, { cmId: this.cmId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Add responses and format items.
 | 
				
			||||||
 | 
					            this.items = <AddonModFeedbackAttemptItem[]> items.items.map((item) => {
 | 
				
			||||||
 | 
					                const formItem = AddonModFeedbackHelper.getItemForm(item, true);
 | 
				
			||||||
 | 
					                if (!formItem) {
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const attemptItem = <AddonModFeedbackAttemptItem> formItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (item.typ == 'label') {
 | 
				
			||||||
 | 
					                    attemptItem.submittedValue = CoreTextUtils.replacePluginfileUrls(item.presentation, item.itemfiles);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    for (const x in attempt.responses) {
 | 
				
			||||||
 | 
					                        if (attempt.responses[x].id == item.id) {
 | 
				
			||||||
 | 
					                            attemptItem.submittedValue = attempt.responses[x].printval;
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return attemptItem;
 | 
				
			||||||
 | 
					            }).filter((itemData) => itemData); // Filter items with errors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } catch (message) {
 | 
				
			||||||
 | 
					            // Some call failed on fetch, go back.
 | 
				
			||||||
 | 
					            CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
 | 
				
			||||||
 | 
					            CoreNavigator.back();
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            this.loaded = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check if an attempt is anonymous or not.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param attempt Attempt to check.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isAnonAttempt(attempt: AddonModFeedbackWSAttempt | AddonModFeedbackWSAnonAttempt): attempt is AddonModFeedbackWSAnonAttempt {
 | 
				
			||||||
 | 
					        return !('fullname' in attempt);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AddonModFeedbackAttemptItem = AddonModFeedbackFormItem & {
 | 
				
			||||||
 | 
					    submittedValue?: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										178
									
								
								src/addons/mod/feedback/pages/form/form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/addons/mod/feedback/pages/form/form.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,178 @@
 | 
				
			|||||||
 | 
					<ion-header>
 | 
				
			||||||
 | 
					    <ion-toolbar>
 | 
				
			||||||
 | 
					        <ion-buttons slot="start">
 | 
				
			||||||
 | 
					            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
				
			||||||
 | 
					        </ion-buttons>
 | 
				
			||||||
 | 
					        <ion-title>
 | 
				
			||||||
 | 
					            <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId">
 | 
				
			||||||
 | 
					            </core-format-text>
 | 
				
			||||||
 | 
					        </ion-title>
 | 
				
			||||||
 | 
					    </ion-toolbar>
 | 
				
			||||||
 | 
					</ion-header>
 | 
				
			||||||
 | 
					<ion-content>
 | 
				
			||||||
 | 
					    <core-loading [hideUntil]="feedbackLoaded">
 | 
				
			||||||
 | 
					        <ng-container *ngIf="items && items.length">
 | 
				
			||||||
 | 
					            <ion-list class="ion-no-margin">
 | 
				
			||||||
 | 
					                <ion-item class="ion-text-wrap">
 | 
				
			||||||
 | 
					                    <ion-label>
 | 
				
			||||||
 | 
					                        <h2>{{ 'addon.mod_feedback.mode' | translate }}</h2>
 | 
				
			||||||
 | 
					                        <p *ngIf="access!.isanonymous">{{ 'addon.mod_feedback.anonymous' | translate }}</p>
 | 
				
			||||||
 | 
					                        <p *ngIf="!access!.isanonymous">{{ 'addon.mod_feedback.non_anonymous' | translate }}</p>
 | 
				
			||||||
 | 
					                    </ion-label>
 | 
				
			||||||
 | 
					                </ion-item>
 | 
				
			||||||
 | 
					                <ng-container *ngFor="let item of items">
 | 
				
			||||||
 | 
					                    <ion-item-divider *ngIf="item.typ == 'pagebreak'">
 | 
				
			||||||
 | 
					                        <ion-label></ion-label>
 | 
				
			||||||
 | 
					                    </ion-item-divider>
 | 
				
			||||||
 | 
					                    <ng-container *ngIf="item.typ != 'pagebreak'">
 | 
				
			||||||
 | 
					                        <ion-item class="ion-text-wrap addon-mod_feedback-item" [color]="item.dependitem > 0 ? 'light' : ''"
 | 
				
			||||||
 | 
					                            [class.core-danger-item]="item.isEmpty || item.hasError">
 | 
				
			||||||
 | 
					                            <ion-label [position]="item.hasTextInput ? 'stacked' : undefined">
 | 
				
			||||||
 | 
					                                <p *ngIf="item.name" [core-mark-required]="item.required">
 | 
				
			||||||
 | 
					                                    <span *ngIf="feedback!.autonumbering && item.itemnumber">{{item.itemnumber}}. </span>
 | 
				
			||||||
 | 
					                                    <core-format-text [component]="component" [componentId]="cmId" [text]="item.name"
 | 
				
			||||||
 | 
					                                        contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId" [wsNotFiltered]="true">
 | 
				
			||||||
 | 
					                                    </core-format-text>
 | 
				
			||||||
 | 
					                                    <span *ngIf="item.postfix" class="addon-mod_feedback-postfix">{{item.postfix}}</span>
 | 
				
			||||||
 | 
					                                </p>
 | 
				
			||||||
 | 
					                                <p *ngIf="item.templateName == 'label'">
 | 
				
			||||||
 | 
					                                    <core-format-text [component]="component" [componentId]="cmId"
 | 
				
			||||||
 | 
					                                        [text]="item.presentation" contextLevel="module" [contextInstanceId]="cmId"
 | 
				
			||||||
 | 
					                                        [wsNotFiltered]="true" [courseId]="courseId">
 | 
				
			||||||
 | 
					                                    </core-format-text>
 | 
				
			||||||
 | 
					                                </p>
 | 
				
			||||||
 | 
					                            </ion-label>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <ion-input *ngIf="item.templateName == 'textfield'" type="text" [(ngModel)]="item.value"
 | 
				
			||||||
 | 
					                                autocorrect="off" name="{{item.typ}}_{{item.id}}" maxlength="{{item.length}}"
 | 
				
			||||||
 | 
					                                [required]="item.required">
 | 
				
			||||||
 | 
					                            </ion-input>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <ng-container *ngIf="item.templateName == 'numeric'">
 | 
				
			||||||
 | 
					                                <ion-input type="number" [(ngModel)]="item.value" name="{{item.typ}}_{{item.id}}"
 | 
				
			||||||
 | 
					                                    [required]="item.required">
 | 
				
			||||||
 | 
					                                </ion-input>
 | 
				
			||||||
 | 
					                                <ion-text *ngIf="item.hasError" color="danger" class="addon-mod_feedback-item-error">
 | 
				
			||||||
 | 
					                                    {{ 'addon.mod_feedback.numberoutofrange' | translate }} [{{item.rangefrom}}
 | 
				
			||||||
 | 
					                                    <span *ngIf="item.rangefrom && item.rangeto">, </span>{{item.rangeto}}]
 | 
				
			||||||
 | 
					                                </ion-text>
 | 
				
			||||||
 | 
					                            </ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <ion-textarea *ngIf="item.templateName == 'textarea'" [required]="item.required"
 | 
				
			||||||
 | 
					                                name="{{item.typ}}_{{item.id}}" [attr.aria-multiline]="true" [(ngModel)]="item.value">
 | 
				
			||||||
 | 
					                            </ion-textarea>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <ion-select *ngIf="item.templateName == 'multichoice-d'" [required]="item.required"
 | 
				
			||||||
 | 
					                                name="{{item.typ}}_{{item.id}}" [(ngModel)]="item.value" interface="action-sheet">
 | 
				
			||||||
 | 
					                                <ion-select-option *ngFor="let option of item.choices" [value]="option.value">
 | 
				
			||||||
 | 
					                                    <core-format-text [component]="component" [componentId]="cmId" [text]="option.label"
 | 
				
			||||||
 | 
					                                        contextLevel="module" [contextInstanceId]="cmId" [wsNotFiltered]="true"
 | 
				
			||||||
 | 
					                                        [courseId]="courseId">
 | 
				
			||||||
 | 
					                                    </core-format-text>
 | 
				
			||||||
 | 
					                                </ion-select-option>
 | 
				
			||||||
 | 
					                            </ion-select>
 | 
				
			||||||
 | 
					                        </ion-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <ion-radio-group *ngIf="item.templateName == 'multichoice-r'" [(ngModel)]="item.value"
 | 
				
			||||||
 | 
					                            [required]="item.required" name="{{item.typ}}_{{item.id}}">
 | 
				
			||||||
 | 
					                            <ion-item *ngFor="let option of item.choices">
 | 
				
			||||||
 | 
					                                <ion-label>
 | 
				
			||||||
 | 
					                                    <core-format-text  [component]="component" [componentId]="cmId"
 | 
				
			||||||
 | 
					                                        [text]="option.label" contextLevel="module" [contextInstanceId]="cmId"
 | 
				
			||||||
 | 
					                                        [wsNotFiltered]="true" [courseId]="courseId">
 | 
				
			||||||
 | 
					                                    </core-format-text>
 | 
				
			||||||
 | 
					                                </ion-label>
 | 
				
			||||||
 | 
					                                <ion-radio slot="start" [value]="option.value"></ion-radio>
 | 
				
			||||||
 | 
					                            </ion-item>
 | 
				
			||||||
 | 
					                        </ion-radio-group>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <ng-container *ngIf="item.templateName == 'multichoice-c'">
 | 
				
			||||||
 | 
					                            <ion-item *ngFor="let option of item.choices">
 | 
				
			||||||
 | 
					                                <ion-label>
 | 
				
			||||||
 | 
					                                    <core-format-text [component]="component" [componentId]="cmId" [text]="option.label"
 | 
				
			||||||
 | 
					                                        contextLevel="module" [contextInstanceId]="cmId" [wsNotFiltered]="true"
 | 
				
			||||||
 | 
					                                        [courseId]="courseId">
 | 
				
			||||||
 | 
					                                    </core-format-text>
 | 
				
			||||||
 | 
					                                </ion-label>
 | 
				
			||||||
 | 
					                                <ion-checkbox [required]="item.required" name="{{item.typ}}_{{item.id}}"
 | 
				
			||||||
 | 
					                                    [(ngModel)]="option.checked" value="option.value">
 | 
				
			||||||
 | 
					                                </ion-checkbox>
 | 
				
			||||||
 | 
					                            </ion-item>
 | 
				
			||||||
 | 
					                        </ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        <ng-container *ngIf="item.templateName == 'captcha'">
 | 
				
			||||||
 | 
					                            <core-recaptcha *ngIf="!preview && !offline" [publicKey]="item.captcha.recaptchapublickey"
 | 
				
			||||||
 | 
					                                [model]="item" modelValueName="value">
 | 
				
			||||||
 | 
					                            </core-recaptcha>
 | 
				
			||||||
 | 
					                            <div *ngIf="!preview && (!item.captcha || offline)" class="core-warning-card">
 | 
				
			||||||
 | 
					                                <ion-item>
 | 
				
			||||||
 | 
					                                    <ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
 | 
				
			||||||
 | 
					                                    <ion-label>{{ 'addon.mod_feedback.captchaofflinewarning' | translate }}</ion-label>
 | 
				
			||||||
 | 
					                                </ion-item>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </ng-container>
 | 
				
			||||||
 | 
					                    </ng-container>
 | 
				
			||||||
 | 
					                </ng-container>
 | 
				
			||||||
 | 
					                <ion-grid *ngIf="!preview">
 | 
				
			||||||
 | 
					                    <ion-row class="ion-align-items-center">
 | 
				
			||||||
 | 
					                        <ion-col *ngIf="hasPrevPage">
 | 
				
			||||||
 | 
					                            <ion-button expand="block" fill="outline" (click)="gotoPage(true)" class="ion-text-wrap">
 | 
				
			||||||
 | 
					                                <ion-icon name="fas-chevron-left" slot="start"></ion-icon>
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.previous_page' | translate }}
 | 
				
			||||||
 | 
					                            </ion-button>
 | 
				
			||||||
 | 
					                        </ion-col>
 | 
				
			||||||
 | 
					                        <ion-col *ngIf="hasNextPage">
 | 
				
			||||||
 | 
					                            <ion-button expand="block" (click)="gotoPage(false)" class="ion-text-wrap">
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.next_page' | translate }}
 | 
				
			||||||
 | 
					                                <ion-icon name="fas-chevron-right" slot="end"></ion-icon>
 | 
				
			||||||
 | 
					                            </ion-button>
 | 
				
			||||||
 | 
					                        </ion-col>
 | 
				
			||||||
 | 
					                        <ion-col *ngIf="!hasNextPage">
 | 
				
			||||||
 | 
					                            <ion-button expand="block" (click)="gotoPage(false)" class="ion-text-wrap">
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.save_entries' | translate }}
 | 
				
			||||||
 | 
					                            </ion-button>
 | 
				
			||||||
 | 
					                        </ion-col>
 | 
				
			||||||
 | 
					                    </ion-row>
 | 
				
			||||||
 | 
					                </ion-grid>
 | 
				
			||||||
 | 
					            </ion-list>
 | 
				
			||||||
 | 
					        </ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ion-card class="core-success-card" *ngIf="completed">
 | 
				
			||||||
 | 
					            <ion-item>
 | 
				
			||||||
 | 
					                <ion-icon name="fas-check" slot="start"></ion-icon>
 | 
				
			||||||
 | 
					                <ion-label>
 | 
				
			||||||
 | 
					                    <p *ngIf="!completionPageContents && !completedOffline">
 | 
				
			||||||
 | 
					                        {{ 'addon.mod_feedback.this_feedback_is_already_submitted' | translate }}
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                    <p *ngIf="!completionPageContents && completedOffline">
 | 
				
			||||||
 | 
					                        {{ 'addon.mod_feedback.feedback_submitted_offline' | translate }}
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                    <p *ngIf="completionPageContents">
 | 
				
			||||||
 | 
					                        <core-format-text  [component]="component" componentId="componentId" [text]="completionPageContents"
 | 
				
			||||||
 | 
					                            contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId">
 | 
				
			||||||
 | 
					                        </core-format-text>
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                </ion-label>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					        </ion-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ion-card>
 | 
				
			||||||
 | 
					            <ion-grid *ngIf="completed">
 | 
				
			||||||
 | 
					                <ion-row class="ion-align-items-center">
 | 
				
			||||||
 | 
					                    <ion-col *ngIf="access!.canviewanalysis">
 | 
				
			||||||
 | 
					                        <ion-button expand="block" fill="outline" (click)="showAnalysis()" class="ion-text-wrap">
 | 
				
			||||||
 | 
					                            <ion-icon name="fas-chart-bar" slot="start"></ion-icon>
 | 
				
			||||||
 | 
					                            {{ 'addon.mod_feedback.completed_feedbacks' | translate }}
 | 
				
			||||||
 | 
					                        </ion-button>
 | 
				
			||||||
 | 
					                    </ion-col>
 | 
				
			||||||
 | 
					                    <ion-col *ngIf="hasNextPage">
 | 
				
			||||||
 | 
					                        <ion-button expand="block" (click)="continue()" class="ion-text-wrap">
 | 
				
			||||||
 | 
					                            {{ 'core.continue' | translate }}
 | 
				
			||||||
 | 
					                            <ion-icon name="fas-chevron-right" slot="end"></ion-icon>
 | 
				
			||||||
 | 
					                        </ion-button>
 | 
				
			||||||
 | 
					                    </ion-col>
 | 
				
			||||||
 | 
					                </ion-row>
 | 
				
			||||||
 | 
					            </ion-grid>
 | 
				
			||||||
 | 
					        </ion-card>
 | 
				
			||||||
 | 
					    </core-loading>
 | 
				
			||||||
 | 
					</ion-content>
 | 
				
			||||||
							
								
								
									
										39
									
								
								src/addons/mod/feedback/pages/form/form.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/addons/mod/feedback/pages/form/form.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					// (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 { CoreSharedModule } from '@/core/shared.module';
 | 
				
			||||||
 | 
					import { NgModule } from '@angular/core';
 | 
				
			||||||
 | 
					import { RouterModule, Routes } from '@angular/router';
 | 
				
			||||||
 | 
					import { CanLeaveGuard } from '@guards/can-leave';
 | 
				
			||||||
 | 
					import { AddonModFeedbackFormPage } from './form';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const routes: Routes = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: '',
 | 
				
			||||||
 | 
					        component: AddonModFeedbackFormPage,
 | 
				
			||||||
 | 
					        canDeactivate: [CanLeaveGuard],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					    declarations: [
 | 
				
			||||||
 | 
					        AddonModFeedbackFormPage,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    imports: [
 | 
				
			||||||
 | 
					        RouterModule.forChild(routes),
 | 
				
			||||||
 | 
					        CoreSharedModule,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    exports: [RouterModule],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackFormPageModule {}
 | 
				
			||||||
							
								
								
									
										11
									
								
								src/addons/mod/feedback/pages/form/form.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/addons/mod/feedback/pages/form/form.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					:host {
 | 
				
			||||||
 | 
					    .addon-mod_feedback-item ion-label.label-stacked {
 | 
				
			||||||
 | 
					        margin: 11px 0px 10px;
 | 
				
			||||||
 | 
					        transform: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .addon-mod_feedback-item-error {
 | 
				
			||||||
 | 
					        padding-top: 5px;
 | 
				
			||||||
 | 
					        padding-bottom: 8px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										428
									
								
								src/addons/mod/feedback/pages/form/form.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										428
									
								
								src/addons/mod/feedback/pages/form/form.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,428 @@
 | 
				
			|||||||
 | 
					// (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 { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
 | 
				
			||||||
 | 
					import { CoreSite } from '@classes/site';
 | 
				
			||||||
 | 
					import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
 | 
				
			||||||
 | 
					import { CoreCourse, CoreCourseCommonModWSOptions, CoreCourseWSModule } from '@features/course/services/course';
 | 
				
			||||||
 | 
					import { CoreCourseHelper } from '@features/course/services/course-helper';
 | 
				
			||||||
 | 
					import { CanLeave } from '@guards/can-leave';
 | 
				
			||||||
 | 
					import { IonContent } from '@ionic/angular';
 | 
				
			||||||
 | 
					import { CoreApp } from '@services/app';
 | 
				
			||||||
 | 
					import { CoreNavigator } from '@services/navigator';
 | 
				
			||||||
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
 | 
					import { Network, NgZone, Translate } from '@singletons';
 | 
				
			||||||
 | 
					import { CoreEvents } from '@singletons/events';
 | 
				
			||||||
 | 
					import { Subscription } from 'rxjs';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    AddonModFeedback,
 | 
				
			||||||
 | 
					    AddonModFeedbackGetFeedbackAccessInformationWSResponse,
 | 
				
			||||||
 | 
					    AddonModFeedbackPageItems,
 | 
				
			||||||
 | 
					    AddonModFeedbackProvider,
 | 
				
			||||||
 | 
					    AddonModFeedbackResponseValue,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSFeedback,
 | 
				
			||||||
 | 
					} from '../../services/feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackFormItem, AddonModFeedbackHelper } from '../../services/feedback-helper';
 | 
				
			||||||
 | 
					import { AddonModFeedbackSync } from '../../services/feedback-sync';
 | 
				
			||||||
 | 
					import { AddonModFeedbackModuleHandlerService } from '../../services/handlers/module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Page that displays feedback form.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					    selector: 'page-addon-mod-feedback-form',
 | 
				
			||||||
 | 
					    templateUrl: 'form.html',
 | 
				
			||||||
 | 
					    styleUrls: ['form.scss'],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackFormPage implements OnInit, OnDestroy, CanLeave {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ViewChild(IonContent) content?: IonContent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected module?: CoreCourseWSModule;
 | 
				
			||||||
 | 
					    protected currentPage?: number;
 | 
				
			||||||
 | 
					    protected siteAfterSubmit?: string;
 | 
				
			||||||
 | 
					    protected onlineObserver: Subscription;
 | 
				
			||||||
 | 
					    protected originalData?: Record<string, AddonModFeedbackResponseValue>;
 | 
				
			||||||
 | 
					    protected currentSite: CoreSite;
 | 
				
			||||||
 | 
					    protected forceLeave = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    title?: string;
 | 
				
			||||||
 | 
					    preview = false;
 | 
				
			||||||
 | 
					    cmId!: number;
 | 
				
			||||||
 | 
					    courseId!: number;
 | 
				
			||||||
 | 
					    feedback?: AddonModFeedbackWSFeedback;
 | 
				
			||||||
 | 
					    completionPageContents?: string;
 | 
				
			||||||
 | 
					    component = AddonModFeedbackProvider.COMPONENT;
 | 
				
			||||||
 | 
					    offline = false;
 | 
				
			||||||
 | 
					    feedbackLoaded = false;
 | 
				
			||||||
 | 
					    access?: AddonModFeedbackGetFeedbackAccessInformationWSResponse;
 | 
				
			||||||
 | 
					    items: AddonModFeedbackFormItem[] = [];
 | 
				
			||||||
 | 
					    hasPrevPage = false;
 | 
				
			||||||
 | 
					    hasNextPage = false;
 | 
				
			||||||
 | 
					    completed = false;
 | 
				
			||||||
 | 
					    completedOffline = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        this.currentSite = CoreSites.getCurrentSite()!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Refresh online status when changes.
 | 
				
			||||||
 | 
					        this.onlineObserver = Network.onChange().subscribe(() => {
 | 
				
			||||||
 | 
					            // Execute the callback in the Angular zone, so change detection doesn't stop working.
 | 
				
			||||||
 | 
					            NgZone.run(() => {
 | 
				
			||||||
 | 
					                this.offline = !CoreApp.isOnline();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async ngOnInit(): Promise<void> {
 | 
				
			||||||
 | 
					        this.cmId = CoreNavigator.getRouteNumberParam('cmId')!;
 | 
				
			||||||
 | 
					        this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
 | 
				
			||||||
 | 
					        this.currentPage = CoreNavigator.getRouteNumberParam('page');
 | 
				
			||||||
 | 
					        this.title = CoreNavigator.getRouteParam('title');
 | 
				
			||||||
 | 
					        this.preview = !!CoreNavigator.getRouteBooleanParam('preview');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await this.fetchData();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.feedback) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await AddonModFeedback.logView(this.feedback.id, this.feedback.name, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            CoreCourse.checkModuleCompletion(this.courseId, this.module!.completiondata);
 | 
				
			||||||
 | 
					        } catch {
 | 
				
			||||||
 | 
					            // Ignore errors.
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * View entered.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ionViewDidEnter(): void {
 | 
				
			||||||
 | 
					        this.forceLeave = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async canLeave(): Promise<boolean> {
 | 
				
			||||||
 | 
					        if (this.forceLeave) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.preview) {
 | 
				
			||||||
 | 
					            const responses = AddonModFeedbackHelper.getPageItemsResponses(this.items);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.items && !this.completed && this.originalData) {
 | 
				
			||||||
 | 
					                // Form submitted. Check if there is any change.
 | 
				
			||||||
 | 
					                if (!CoreUtils.basicLeftCompare(responses, this.originalData, 3)) {
 | 
				
			||||||
 | 
					                    await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit'));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetch all the data required for the view.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async fetchData(): Promise<void> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            this.module = await CoreCourse.getModule(this.cmId, this.courseId, undefined, true, false, this.currentSite.getId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.offline = !CoreApp.isOnline();
 | 
				
			||||||
 | 
					            const options = {
 | 
				
			||||||
 | 
					                cmId: this.cmId,
 | 
				
			||||||
 | 
					                readingStrategy: this.offline ? CoreSitesReadingStrategy.PreferCache : CoreSitesReadingStrategy.OnlyNetwork,
 | 
				
			||||||
 | 
					                siteId: this.currentSite.getId(),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.feedback = await AddonModFeedback.getFeedback(this.courseId, this.cmId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.title = this.feedback.name || this.title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await this.fetchAccessData(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let page = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!this.preview && this.access!.cansubmit && !this.access!.isempty) {
 | 
				
			||||||
 | 
					                page = this.currentPage ?? await this.fetchResumePage(options);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                this.preview = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await this.fetchFeedbackPageData(page);
 | 
				
			||||||
 | 
					        } catch (message) {
 | 
				
			||||||
 | 
					            CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
 | 
				
			||||||
 | 
					            this.forceLeave = true;
 | 
				
			||||||
 | 
					            CoreNavigator.back();
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            this.feedbackLoaded = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetch access information.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param options Options.
 | 
				
			||||||
 | 
					     * @return Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async fetchAccessData(options: CoreCourseCommonModWSOptions): Promise<void> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            this.access = await AddonModFeedback.getFeedbackAccessInformation(this.feedback!.id, options);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            if (this.offline || CoreUtils.isWebServiceError(error)) {
 | 
				
			||||||
 | 
					                // Already offline or shouldn't go offline, fail.
 | 
				
			||||||
 | 
					                throw error;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // If it fails, go offline.
 | 
				
			||||||
 | 
					            this.offline = true;
 | 
				
			||||||
 | 
					            options.readingStrategy = CoreSitesReadingStrategy.PreferCache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.access = await AddonModFeedback.getFeedbackAccessInformation(this.feedback!.id, options);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get resume page from WS.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param options Options.
 | 
				
			||||||
 | 
					     * @return Promise resolved with the page to resume.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async fetchResumePage(options: CoreCourseCommonModWSOptions): Promise<number> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return await AddonModFeedback.getResumePage(this.feedback!.id, options);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            if (this.offline || CoreUtils.isWebServiceError(error)) {
 | 
				
			||||||
 | 
					                // Already offline or shouldn't go offline, fail.
 | 
				
			||||||
 | 
					                throw error;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Go offline.
 | 
				
			||||||
 | 
					            this.offline = true;
 | 
				
			||||||
 | 
					            options.readingStrategy = CoreSitesReadingStrategy.PreferCache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return AddonModFeedback.getResumePage(this.feedback!.id, options);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetch page data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param page Page to load.
 | 
				
			||||||
 | 
					     * @return Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async fetchFeedbackPageData(page: number = 0): Promise<void> {
 | 
				
			||||||
 | 
					        this.items = [];
 | 
				
			||||||
 | 
					        const response = await this.fetchPageItems(page);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.items = <AddonModFeedbackFormItem[]> response.items
 | 
				
			||||||
 | 
					            .map((itemData) => AddonModFeedbackHelper.getItemForm(itemData, this.preview))
 | 
				
			||||||
 | 
					            .filter((itemData) => itemData); // Filter items with errors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.preview) {
 | 
				
			||||||
 | 
					            const itemsCopy = CoreUtils.clone(this.items); // Copy the array to avoid modifications.
 | 
				
			||||||
 | 
					            this.originalData = AddonModFeedbackHelper.getPageItemsResponses(itemsCopy);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetch page items.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param page Page to get.
 | 
				
			||||||
 | 
					     * @return Promise resolved with WS response.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async fetchPageItems(page: number): Promise<AddonModFeedbackPageItems> {
 | 
				
			||||||
 | 
					        const options = {
 | 
				
			||||||
 | 
					            cmId: this.cmId,
 | 
				
			||||||
 | 
					            readingStrategy: this.offline ? CoreSitesReadingStrategy.PreferCache : CoreSitesReadingStrategy.OnlyNetwork,
 | 
				
			||||||
 | 
					            siteId: this.currentSite.getId(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.preview) {
 | 
				
			||||||
 | 
					            const response = await AddonModFeedback.getItems(this.feedback!.id, options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                items: response.items,
 | 
				
			||||||
 | 
					                warnings: response.warnings,
 | 
				
			||||||
 | 
					                hasnextpage: false,
 | 
				
			||||||
 | 
					                hasprevpage: false,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.currentPage = page;
 | 
				
			||||||
 | 
					        let response: AddonModFeedbackPageItems;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            response = await AddonModFeedback.getPageItemsWithValues(this.feedback!.id, page, options);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            if (this.offline || CoreUtils.isWebServiceError(error)) {
 | 
				
			||||||
 | 
					                // Already offline or shouldn't go offline, fail.
 | 
				
			||||||
 | 
					                throw error;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Go offline.
 | 
				
			||||||
 | 
					            this.offline = true;
 | 
				
			||||||
 | 
					            options.readingStrategy = CoreSitesReadingStrategy.PreferCache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            response = await AddonModFeedback.getPageItemsWithValues(this.feedback!.id, page, options);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.hasPrevPage = !!response.hasprevpage;
 | 
				
			||||||
 | 
					        this.hasNextPage = !!response.hasnextpage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return response;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Function to allow page navigation through the questions form.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param goPrevious If true it will go back to the previous page, if false, it will go forward.
 | 
				
			||||||
 | 
					     * @return Resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async gotoPage(goPrevious: boolean): Promise<void> {
 | 
				
			||||||
 | 
					        this.content?.scrollToTop();
 | 
				
			||||||
 | 
					        this.feedbackLoaded = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const responses = AddonModFeedbackHelper.getPageItemsResponses(this.items);
 | 
				
			||||||
 | 
					        const formHasErrors = this.items.some((item) => item.isEmpty || item.hasError);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            // Sync other pages first.
 | 
				
			||||||
 | 
					            await CoreUtils.ignoreErrors(AddonModFeedbackSync.syncFeedback(this.feedback!.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const response = await AddonModFeedback.processPage(this.feedback!.id, this.currentPage!, responses, {
 | 
				
			||||||
 | 
					                goPrevious,
 | 
				
			||||||
 | 
					                formHasErrors,
 | 
				
			||||||
 | 
					                courseId: this.courseId,
 | 
				
			||||||
 | 
					                cmId: this.cmId,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (response.completed) {
 | 
				
			||||||
 | 
					                // Form is completed, show completion message and buttons.
 | 
				
			||||||
 | 
					                this.items = [];
 | 
				
			||||||
 | 
					                this.completed = true;
 | 
				
			||||||
 | 
					                this.completedOffline = !!response.offline;
 | 
				
			||||||
 | 
					                this.completionPageContents = response.completionpagecontents;
 | 
				
			||||||
 | 
					                this.siteAfterSubmit = response.siteaftersubmit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'feedback' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Invalidate access information so user will see home page updated (continue form or completion messages).
 | 
				
			||||||
 | 
					                await Promise.all([
 | 
				
			||||||
 | 
					                    AddonModFeedback.invalidateFeedbackAccessInformationData(this.feedback!.id),
 | 
				
			||||||
 | 
					                    AddonModFeedback.invalidateResumePageData(this.feedback!.id),
 | 
				
			||||||
 | 
					                ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // If form has been submitted, the info has been already invalidated but we should update index view.
 | 
				
			||||||
 | 
					                CoreEvents.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, {
 | 
				
			||||||
 | 
					                    feedbackId: this.feedback!.id,
 | 
				
			||||||
 | 
					                    tab: 'overview',
 | 
				
			||||||
 | 
					                    offline: this.completedOffline,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await this.fetchAccessData({
 | 
				
			||||||
 | 
					                    cmId: this.cmId,
 | 
				
			||||||
 | 
					                    readingStrategy: this.offline ? CoreSitesReadingStrategy.PreferCache : CoreSitesReadingStrategy.OnlyNetwork,
 | 
				
			||||||
 | 
					                    siteId: this.currentSite.getId(),
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            } else if (typeof response.jumpto != 'number' || response.jumpto == this.currentPage) {
 | 
				
			||||||
 | 
					                // Errors on questions, stay in page.
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Invalidate access information so user will see home page updated (continue form).
 | 
				
			||||||
 | 
					                await AddonModFeedback.invalidateResumePageData(this.feedback!.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                CoreEvents.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, {
 | 
				
			||||||
 | 
					                    feedbackId: this.feedback!.id,
 | 
				
			||||||
 | 
					                    tab: 'overview',
 | 
				
			||||||
 | 
					                    offline: this.completedOffline,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Fetch the new page.
 | 
				
			||||||
 | 
					                await this.fetchFeedbackPageData(response.jumpto);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (message) {
 | 
				
			||||||
 | 
					            CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            this.feedbackLoaded = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Function to link implemented features.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    showAnalysis(): void {
 | 
				
			||||||
 | 
					        const indexPath = AddonModFeedbackModuleHandlerService.PAGE_NAME + `/${this.courseId}/${this.cmId}`;
 | 
				
			||||||
 | 
					        const previousPath = CoreNavigator.getPreviousPath();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (previousPath.match(new RegExp(indexPath + '$'))) {
 | 
				
			||||||
 | 
					            // Previous page is the index page, go back.
 | 
				
			||||||
 | 
					            CoreEvents.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, {
 | 
				
			||||||
 | 
					                feedbackId: this.feedback!.id,
 | 
				
			||||||
 | 
					                tab: 'analysis',
 | 
				
			||||||
 | 
					                offline: this.completedOffline,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            CoreNavigator.back();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        CoreNavigator.navigateToSitePath(indexPath, {
 | 
				
			||||||
 | 
					            params: {
 | 
				
			||||||
 | 
					                module: this.module,
 | 
				
			||||||
 | 
					                tab: 'analysis',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Function to go to the page after submit.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async continue(): Promise<void> {
 | 
				
			||||||
 | 
					        if (!this.siteAfterSubmit) {
 | 
				
			||||||
 | 
					            return CoreCourseHelper.getAndOpenCourse(this.courseId, {}, this.currentSite.getId());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const modal = await CoreDomUtils.showModalLoading();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const treated = await CoreContentLinksHelper.handleLink(this.siteAfterSubmit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!treated) {
 | 
				
			||||||
 | 
					                await this.currentSite.openInBrowserWithAutoLoginIfSameSite(this.siteAfterSubmit);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            modal.dismiss();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Component being destroyed.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ngOnDestroy(): void {
 | 
				
			||||||
 | 
					        this.onlineObserver.unsubscribe();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/addons/mod/feedback/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/addons/mod/feedback/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					<ion-header>
 | 
				
			||||||
 | 
					    <ion-toolbar>
 | 
				
			||||||
 | 
					        <ion-buttons slot="start">
 | 
				
			||||||
 | 
					            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
				
			||||||
 | 
					        </ion-buttons>
 | 
				
			||||||
 | 
					        <ion-title>
 | 
				
			||||||
 | 
					            <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
 | 
				
			||||||
 | 
					            </core-format-text>
 | 
				
			||||||
 | 
					        </ion-title>
 | 
				
			||||||
 | 
					        <ion-buttons slot="end">
 | 
				
			||||||
 | 
					            <!-- The buttons defined by the component will be added in here. -->
 | 
				
			||||||
 | 
					        </ion-buttons>
 | 
				
			||||||
 | 
					    </ion-toolbar>
 | 
				
			||||||
 | 
					</ion-header>
 | 
				
			||||||
 | 
					<ion-content>
 | 
				
			||||||
 | 
					    <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event.target)">
 | 
				
			||||||
 | 
					        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
				
			||||||
 | 
					    </ion-refresher>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <addon-mod-feedback-index [module]="module" [courseId]="courseId" [group]="selectedGroup" [tab]="selectedTab"
 | 
				
			||||||
 | 
					        (dataRetrieved)="updateData($event)"></addon-mod-feedback-index>
 | 
				
			||||||
 | 
					</ion-content>
 | 
				
			||||||
							
								
								
									
										43
									
								
								src/addons/mod/feedback/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/addons/mod/feedback/pages/index/index.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 { Component, OnInit, ViewChild } from '@angular/core';
 | 
				
			||||||
 | 
					import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page';
 | 
				
			||||||
 | 
					import { CoreNavigator } from '@services/navigator';
 | 
				
			||||||
 | 
					import { AddonModFeedbackIndexComponent } from '../../components/index/index';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Page that displays a feedback.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					    selector: 'page-addon-mod-feedback-index',
 | 
				
			||||||
 | 
					    templateUrl: 'index.html',
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackIndexPage extends CoreCourseModuleMainActivityPage<AddonModFeedbackIndexComponent> implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ViewChild(AddonModFeedbackIndexComponent) activityComponent?: AddonModFeedbackIndexComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    selectedTab?: string;
 | 
				
			||||||
 | 
					    selectedGroup?: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Component being initialized.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ngOnInit(): void {
 | 
				
			||||||
 | 
					        super.ngOnInit();
 | 
				
			||||||
 | 
					        this.selectedTab = CoreNavigator.getRouteParam('tab');
 | 
				
			||||||
 | 
					        this.selectedGroup = CoreNavigator.getRouteNumberParam('group');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					<ion-header>
 | 
				
			||||||
 | 
					    <ion-toolbar>
 | 
				
			||||||
 | 
					        <ion-buttons slot="start">
 | 
				
			||||||
 | 
					            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
				
			||||||
 | 
					        </ion-buttons>
 | 
				
			||||||
 | 
					        <ion-title>{{ 'addon.mod_feedback.responses' |translate }}</ion-title>
 | 
				
			||||||
 | 
					    </ion-toolbar>
 | 
				
			||||||
 | 
					</ion-header>
 | 
				
			||||||
 | 
					<ion-content>
 | 
				
			||||||
 | 
					    <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshFeedback($event.target)">
 | 
				
			||||||
 | 
					        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
				
			||||||
 | 
					    </ion-refresher>
 | 
				
			||||||
 | 
					    <core-loading [hideUntil]="loaded">
 | 
				
			||||||
 | 
					        <ion-list class="ion-no-margin">
 | 
				
			||||||
 | 
					            <ion-item class="ion-text-wrap" *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)">
 | 
				
			||||||
 | 
					                <ion-label id="addon-feedback-groupslabel">
 | 
				
			||||||
 | 
					                    <ng-container *ngIf="groupInfo.separateGroups">{{'core.groupsseparate' | translate }}</ng-container>
 | 
				
			||||||
 | 
					                    <ng-container *ngIf="groupInfo.visibleGroups">{{'core.groupsvisible' | translate }}</ng-container>
 | 
				
			||||||
 | 
					                </ion-label>
 | 
				
			||||||
 | 
					                <ion-select [(ngModel)]="selectedGroup" (ionChange)="loadAttempts(selectedGroup)"
 | 
				
			||||||
 | 
					                    aria-labelledby="addon-feedback-groupslabel" interface="action-sheet">
 | 
				
			||||||
 | 
					                    <ion-select-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">
 | 
				
			||||||
 | 
					                        {{groupOpt.name}}
 | 
				
			||||||
 | 
					                    </ion-select-option>
 | 
				
			||||||
 | 
					                </ion-select>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <ion-item-divider>
 | 
				
			||||||
 | 
					                <ion-label>{{ 'addon.mod_feedback.non_respondents_students' | translate : {$a: total } }}</ion-label>
 | 
				
			||||||
 | 
					            </ion-item-divider>
 | 
				
			||||||
 | 
					            <ng-container *ngIf="total > 0">
 | 
				
			||||||
 | 
					                <ion-item *ngFor="let user of users" class="ion-text-wrap">
 | 
				
			||||||
 | 
					                    <core-user-avatar [user]="user" slot="start"></core-user-avatar>
 | 
				
			||||||
 | 
					                    <ion-label>
 | 
				
			||||||
 | 
					                        <h2>{{ user.fullname }}</h2>
 | 
				
			||||||
 | 
					                        <p>
 | 
				
			||||||
 | 
					                            <ion-badge color="success" *ngIf="user.started">
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.started' | translate}}
 | 
				
			||||||
 | 
					                            </ion-badge>
 | 
				
			||||||
 | 
					                            <ion-badge color="danger" *ngIf="!user.started">
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.not_started' | translate}}
 | 
				
			||||||
 | 
					                            </ion-badge>
 | 
				
			||||||
 | 
					                        </p>
 | 
				
			||||||
 | 
					                    </ion-label>
 | 
				
			||||||
 | 
					                </ion-item>
 | 
				
			||||||
 | 
					            </ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <core-infinite-loading [enabled]="canLoadMore" (action)="loadAttempts(undefined, $event)" [error]="loadMoreError">
 | 
				
			||||||
 | 
					            </core-infinite-loading>
 | 
				
			||||||
 | 
					        </ion-list>
 | 
				
			||||||
 | 
					    </core-loading>
 | 
				
			||||||
 | 
					</ion-content>
 | 
				
			||||||
@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					// (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 { CoreSharedModule } from '@/core/shared.module';
 | 
				
			||||||
 | 
					import { NgModule } from '@angular/core';
 | 
				
			||||||
 | 
					import { RouterModule, Routes } from '@angular/router';
 | 
				
			||||||
 | 
					import { AddonModFeedbackNonRespondentsPage } from './nonrespondents';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const routes: Routes = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: '',
 | 
				
			||||||
 | 
					        component: AddonModFeedbackNonRespondentsPage,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					    declarations: [
 | 
				
			||||||
 | 
					        AddonModFeedbackNonRespondentsPage,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    imports: [
 | 
				
			||||||
 | 
					        RouterModule.forChild(routes),
 | 
				
			||||||
 | 
					        CoreSharedModule,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    exports: [RouterModule],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackNonRespondentsPageModule {}
 | 
				
			||||||
							
								
								
									
										165
									
								
								src/addons/mod/feedback/pages/nonrespondents/nonrespondents.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								src/addons/mod/feedback/pages/nonrespondents/nonrespondents.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,165 @@
 | 
				
			|||||||
 | 
					// (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 { Component, OnInit } from '@angular/core';
 | 
				
			||||||
 | 
					import { IonRefresher } from '@ionic/angular';
 | 
				
			||||||
 | 
					import { CoreGroupInfo, CoreGroups } from '@services/groups';
 | 
				
			||||||
 | 
					import { CoreNavigator } from '@services/navigator';
 | 
				
			||||||
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
 | 
					import { AddonModFeedback, AddonModFeedbackWSFeedback } from '../../services/feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackHelper, AddonModFeedbackNonRespondent } from '../../services/feedback-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Page that displays feedback non respondents.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					    selector: 'page-addon-mod-feedback-nonrespondents',
 | 
				
			||||||
 | 
					    templateUrl: 'nonrespondents.html',
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackNonRespondentsPage implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected cmId!: number;
 | 
				
			||||||
 | 
					    protected courseId!: number;
 | 
				
			||||||
 | 
					    protected feedback?: AddonModFeedbackWSFeedback;
 | 
				
			||||||
 | 
					    protected page = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    selectedGroup!: number;
 | 
				
			||||||
 | 
					    groupInfo?: CoreGroupInfo;
 | 
				
			||||||
 | 
					    users: AddonModFeedbackNonRespondent[] = [];
 | 
				
			||||||
 | 
					    total = 0;
 | 
				
			||||||
 | 
					    canLoadMore = false;
 | 
				
			||||||
 | 
					    loaded = false;
 | 
				
			||||||
 | 
					    loadMoreError = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ngOnInit(): void {
 | 
				
			||||||
 | 
					        this.cmId = CoreNavigator.getRouteNumberParam('cmId')!;
 | 
				
			||||||
 | 
					        this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
 | 
				
			||||||
 | 
					        this.selectedGroup = CoreNavigator.getRouteNumberParam('group') || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.fetchData();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetch all the data required for the view.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param refresh Empty events array first.
 | 
				
			||||||
 | 
					     * @return Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async fetchData(refresh: boolean = false): Promise<void> {
 | 
				
			||||||
 | 
					        this.page = 0;
 | 
				
			||||||
 | 
					        this.total = 0;
 | 
				
			||||||
 | 
					        this.users = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            this.feedback = await AddonModFeedback.getFeedback(this.courseId, this.cmId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.groupInfo = await CoreGroups.getActivityGroupInfo(this.cmId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await this.loadGroupUsers(this.selectedGroup);
 | 
				
			||||||
 | 
					        } catch (message) {
 | 
				
			||||||
 | 
					            CoreDomUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!refresh) {
 | 
				
			||||||
 | 
					                // Some call failed on first fetch, go back.
 | 
				
			||||||
 | 
					                CoreNavigator.back();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Load Group responses.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param groupId If defined it will change group if not, it will load more users for the same group.
 | 
				
			||||||
 | 
					     * @return Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async loadGroupUsers(groupId?: number): Promise<void> {
 | 
				
			||||||
 | 
					        this.loadMoreError = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (typeof groupId == 'undefined') {
 | 
				
			||||||
 | 
					            this.page++;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            this.selectedGroup = groupId;
 | 
				
			||||||
 | 
					            this.page = 0;
 | 
				
			||||||
 | 
					            this.total = 0;
 | 
				
			||||||
 | 
					            this.users = [];
 | 
				
			||||||
 | 
					            this.loaded = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const response = await AddonModFeedbackHelper.getNonRespondents(this.feedback!.id, {
 | 
				
			||||||
 | 
					                groupId: this.selectedGroup,
 | 
				
			||||||
 | 
					                page: this.page,
 | 
				
			||||||
 | 
					                cmId: this.cmId,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.total = response.total;
 | 
				
			||||||
 | 
					            if (this.users.length < response.total) {
 | 
				
			||||||
 | 
					                this.users = this.users.concat(response.users);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.canLoadMore = this.users.length < response.total;
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            this.loadMoreError = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            throw error;
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            this.loaded = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Change selected group or load more users.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param groupId Group ID selected. If not defined, it will load more users.
 | 
				
			||||||
 | 
					     * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async loadAttempts(groupId?: number, infiniteComplete?: () => void): Promise<void> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await this.loadGroupUsers(groupId);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            infiniteComplete && infiniteComplete();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Refresh the attempts.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param refresher Refresher.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async refreshFeedback(refresher: IonRefresher): Promise<void> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const promises: Promise<void>[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            promises.push(CoreGroups.invalidateActivityGroupInfo(this.cmId));
 | 
				
			||||||
 | 
					            if (this.feedback) {
 | 
				
			||||||
 | 
					                promises.push(AddonModFeedback.invalidateNonRespondentsData(this.feedback.id));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await CoreUtils.ignoreErrors(Promise.all(promises));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await this.fetchData(true);
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            refresher.complete();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/addons/mod/feedback/pages/respondents/respondents.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/addons/mod/feedback/pages/respondents/respondents.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					<ion-header>
 | 
				
			||||||
 | 
					    <ion-toolbar>
 | 
				
			||||||
 | 
					        <ion-buttons slot="start">
 | 
				
			||||||
 | 
					            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
				
			||||||
 | 
					        </ion-buttons>
 | 
				
			||||||
 | 
					        <ion-title>{{ 'addon.mod_feedback.responses' |translate }}</ion-title>
 | 
				
			||||||
 | 
					    </ion-toolbar>
 | 
				
			||||||
 | 
					</ion-header>
 | 
				
			||||||
 | 
					<ion-content>
 | 
				
			||||||
 | 
					    <core-split-view>
 | 
				
			||||||
 | 
					        <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshFeedback($event.target)">
 | 
				
			||||||
 | 
					            <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
				
			||||||
 | 
					        </ion-refresher>
 | 
				
			||||||
 | 
					        <core-loading [hideUntil]="loaded">
 | 
				
			||||||
 | 
					            <ion-list  class="ion-no-margin">
 | 
				
			||||||
 | 
					                <ion-item class="ion-text-wrap" *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)">
 | 
				
			||||||
 | 
					                    <ion-label id="addon-feedback-groupslabel">
 | 
				
			||||||
 | 
					                        <ng-container *ngIf="groupInfo.separateGroups">{{'core.groupsseparate' | translate }}</ng-container>
 | 
				
			||||||
 | 
					                        <ng-container *ngIf="groupInfo.visibleGroups">{{'core.groupsvisible' | translate }}</ng-container>
 | 
				
			||||||
 | 
					                    </ion-label>
 | 
				
			||||||
 | 
					                    <ion-select [(ngModel)]="selectedGroup" (ionChange)="loadAttempts(selectedGroup)"
 | 
				
			||||||
 | 
					                        aria-labelledby="addon-feedback-groupslabel" interface="action-sheet">
 | 
				
			||||||
 | 
					                        <ion-select-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">
 | 
				
			||||||
 | 
					                            {{groupOpt.name}}
 | 
				
			||||||
 | 
					                        </ion-select-option>
 | 
				
			||||||
 | 
					                    </ion-select>
 | 
				
			||||||
 | 
					                </ion-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <ng-container *ngIf="responses.responses.total > 0">
 | 
				
			||||||
 | 
					                    <ion-item-divider>
 | 
				
			||||||
 | 
					                        <ion-label>
 | 
				
			||||||
 | 
					                            {{ 'addon.mod_feedback.non_anonymous_entries' | translate : {$a: responses.responses.total } }}
 | 
				
			||||||
 | 
					                        </ion-label>
 | 
				
			||||||
 | 
					                    </ion-item-divider>
 | 
				
			||||||
 | 
					                    <ion-item *ngFor="let attempt of responses.responses.attempts" class="ion-text-wrap" tappable detail="true"
 | 
				
			||||||
 | 
					                        (click)="responses.select(attempt)" [class.core-selected-item]="responses.isSelected(attempt)">
 | 
				
			||||||
 | 
					                        <core-user-avatar [user]="attempt" slot="start"></core-user-avatar>
 | 
				
			||||||
 | 
					                        <ion-label>
 | 
				
			||||||
 | 
					                            <h2>{{ attempt.fullname }}</h2>
 | 
				
			||||||
 | 
					                            <p *ngIf="attempt.timemodified">{{attempt.timemodified * 1000 | coreFormatDate }}</p>
 | 
				
			||||||
 | 
					                        </ion-label>
 | 
				
			||||||
 | 
					                    </ion-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <!-- Button and spinner to show more attempts. -->
 | 
				
			||||||
 | 
					                    <ion-button *ngIf="responses.responses.canLoadMore && !loadingMore" class="ion-margin" expand="block"
 | 
				
			||||||
 | 
					                        (click)="loadAttempts()">
 | 
				
			||||||
 | 
					                        {{ 'core.loadmore' | translate }}
 | 
				
			||||||
 | 
					                    </ion-button>
 | 
				
			||||||
 | 
					                    <ion-item *ngIf="responses.responses.canLoadMore && loadingMore" class="ion-text-center">
 | 
				
			||||||
 | 
					                        <ion-label><ion-spinner></ion-spinner></ion-label>
 | 
				
			||||||
 | 
					                    </ion-item>
 | 
				
			||||||
 | 
					                </ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <ng-container *ngIf="responses.anonResponses.total > 0">
 | 
				
			||||||
 | 
					                    <ion-item-divider>
 | 
				
			||||||
 | 
					                        <ion-label>
 | 
				
			||||||
 | 
					                            {{ 'addon.mod_feedback.anonymous_entries' |translate : {$a: responses.anonResponses.total } }}
 | 
				
			||||||
 | 
					                        </ion-label>
 | 
				
			||||||
 | 
					                    </ion-item-divider>
 | 
				
			||||||
 | 
					                    <ion-item *ngFor="let attempt of responses.anonResponses.attempts" class="ion-text-wrap" tappable detail="true"
 | 
				
			||||||
 | 
					                        (click)="responses.select(attempt)" [class.core-selected-item]="responses.isSelected(attempt)">
 | 
				
			||||||
 | 
					                        <ion-label>
 | 
				
			||||||
 | 
					                            <h2>{{ 'addon.mod_feedback.response_nr' |translate }}: {{attempt.number}}</h2>
 | 
				
			||||||
 | 
					                        </ion-label>
 | 
				
			||||||
 | 
					                    </ion-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    <!-- Button and spinner to show more attempts. -->
 | 
				
			||||||
 | 
					                    <ion-button *ngIf="responses.anonResponses.canLoadMore && !loadingMore" class="ion-margin" expand="block"
 | 
				
			||||||
 | 
					                        (click)="loadAttempts()">
 | 
				
			||||||
 | 
					                        {{ 'core.loadmore' | translate }}
 | 
				
			||||||
 | 
					                    </ion-button>
 | 
				
			||||||
 | 
					                    <ion-item *ngIf="responses.anonResponses.canLoadMore && loadingMore" class="ion-text-center">
 | 
				
			||||||
 | 
					                        <ion-label><ion-spinner></ion-spinner></ion-label>
 | 
				
			||||||
 | 
					                    </ion-item>
 | 
				
			||||||
 | 
					                </ng-container>
 | 
				
			||||||
 | 
					            </ion-list>
 | 
				
			||||||
 | 
					        </core-loading>
 | 
				
			||||||
 | 
					    </core-split-view>
 | 
				
			||||||
 | 
					</ion-content>
 | 
				
			||||||
							
								
								
									
										248
									
								
								src/addons/mod/feedback/pages/respondents/respondents.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								src/addons/mod/feedback/pages/respondents/respondents.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,248 @@
 | 
				
			|||||||
 | 
					// (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 { AfterViewInit, Component, ViewChild } from '@angular/core';
 | 
				
			||||||
 | 
					import { ActivatedRoute } from '@angular/router';
 | 
				
			||||||
 | 
					import { CorePageItemsListManager } from '@classes/page-items-list-manager';
 | 
				
			||||||
 | 
					import { CoreSplitViewComponent } from '@components/split-view/split-view';
 | 
				
			||||||
 | 
					import { IonRefresher } from '@ionic/angular';
 | 
				
			||||||
 | 
					import { CoreGroupInfo, CoreGroups } from '@services/groups';
 | 
				
			||||||
 | 
					import { CoreNavigator } from '@services/navigator';
 | 
				
			||||||
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    AddonModFeedback,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSAnonAttempt,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSAttempt,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSFeedback,
 | 
				
			||||||
 | 
					} from '../../services/feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackHelper, AddonModFeedbackResponsesAnalysis } from '../../services/feedback-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Page that displays feedback respondents.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					    selector: 'page-addon-mod-feedback-respondents',
 | 
				
			||||||
 | 
					    templateUrl: 'respondents.html',
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackRespondentsPage implements AfterViewInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected cmId!: number;
 | 
				
			||||||
 | 
					    protected courseId!: number;
 | 
				
			||||||
 | 
					    protected page = 0;
 | 
				
			||||||
 | 
					    protected feedback?: AddonModFeedbackWSFeedback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    responses: AddonModFeedbackResponsesManager;
 | 
				
			||||||
 | 
					    selectedGroup!: number;
 | 
				
			||||||
 | 
					    groupInfo?: CoreGroupInfo;
 | 
				
			||||||
 | 
					    loaded = false;
 | 
				
			||||||
 | 
					    loadingMore = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        route: ActivatedRoute,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        this.responses = new AddonModFeedbackResponsesManager(
 | 
				
			||||||
 | 
					            route.component,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async ngAfterViewInit(): Promise<void> {
 | 
				
			||||||
 | 
					        this.cmId = CoreNavigator.getRouteNumberParam('cmId')!;
 | 
				
			||||||
 | 
					        this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
 | 
				
			||||||
 | 
					        this.selectedGroup = CoreNavigator.getRouteNumberParam('group') || 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await this.fetchData();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.responses.start(this.splitView);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetch all the data required for the view.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param refresh Empty events array first.
 | 
				
			||||||
 | 
					     * @return Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async fetchData(refresh: boolean = false): Promise<void> {
 | 
				
			||||||
 | 
					        this.page = 0;
 | 
				
			||||||
 | 
					        this.responses.resetItems();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            this.feedback = await AddonModFeedback.getFeedback(this.courseId, this.cmId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.groupInfo = await CoreGroups.getActivityGroupInfo(this.cmId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.selectedGroup = CoreGroups.validateGroupId(this.selectedGroup, this.groupInfo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await this.loadGroupAttempts(this.selectedGroup);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!refresh) {
 | 
				
			||||||
 | 
					                // Some call failed on first fetch, go back.
 | 
				
			||||||
 | 
					                CoreNavigator.back();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Load Group attempts.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param groupId If defined it will change group if not, it will load more attempts for the same group.
 | 
				
			||||||
 | 
					     * @return Resolved with the attempts loaded.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async loadGroupAttempts(groupId?: number): Promise<void> {
 | 
				
			||||||
 | 
					        if (typeof groupId == 'undefined') {
 | 
				
			||||||
 | 
					            this.page++;
 | 
				
			||||||
 | 
					            this.loadingMore = true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            this.selectedGroup = groupId;
 | 
				
			||||||
 | 
					            this.page = 0;
 | 
				
			||||||
 | 
					            this.responses.resetItems();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const responses = await AddonModFeedbackHelper.getResponsesAnalysis(this.feedback!.id, {
 | 
				
			||||||
 | 
					                groupId: this.selectedGroup,
 | 
				
			||||||
 | 
					                page: this.page,
 | 
				
			||||||
 | 
					                cmId: this.cmId,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.responses.setResponses(responses);
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            this.loadingMore = false;
 | 
				
			||||||
 | 
					            this.loaded = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Change selected group or load more attempts.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param groupId Group ID selected. If not defined, it will load more attempts.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async loadAttempts(groupId?: number): Promise<void> {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await this.loadGroupAttempts(groupId);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Refresh the attempts.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param refresher Refresher.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async refreshFeedback(refresher: IonRefresher): Promise<void> {
 | 
				
			||||||
 | 
					        const promises: Promise<void>[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        promises.push(CoreGroups.invalidateActivityGroupInfo(this.cmId));
 | 
				
			||||||
 | 
					        if (this.feedback) {
 | 
				
			||||||
 | 
					            promises.push(AddonModFeedback.invalidateResponsesAnalysisData(this.feedback.id));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await CoreUtils.ignoreErrors(Promise.all(promises));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await this.fetchData(true);
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            refresher.complete();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Type of items that can be held by the entries manager.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					type EntryItem = AddonModFeedbackWSAttempt | AddonModFeedbackWSAnonAttempt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Entries manager.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class AddonModFeedbackResponsesManager extends CorePageItemsListManager<EntryItem> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    responses: AddonModFeedbackResponses = {
 | 
				
			||||||
 | 
					        attempts: [],
 | 
				
			||||||
 | 
					        total: 0,
 | 
				
			||||||
 | 
					        canLoadMore: false,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    anonResponses: AddonModFeedbackAnonResponses = {
 | 
				
			||||||
 | 
					        attempts: [],
 | 
				
			||||||
 | 
					        total: 0,
 | 
				
			||||||
 | 
					        canLoadMore: false,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(pageComponent: unknown) {
 | 
				
			||||||
 | 
					        super(pageComponent);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Update responses.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param responses Responses.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setResponses(responses: AddonModFeedbackResponsesAnalysis): void {
 | 
				
			||||||
 | 
					        this.responses.total = responses.totalattempts;
 | 
				
			||||||
 | 
					        this.anonResponses.total = responses.totalanonattempts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.anonResponses.attempts.length < responses.totalanonattempts) {
 | 
				
			||||||
 | 
					            this.anonResponses.attempts = this.anonResponses.attempts.concat(responses.anonattempts);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.responses.attempts.length < responses.totalattempts) {
 | 
				
			||||||
 | 
					            this.responses.attempts = this.responses.attempts.concat(responses.attempts);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.anonResponses.canLoadMore = this.anonResponses.attempts.length < responses.totalanonattempts;
 | 
				
			||||||
 | 
					        this.responses.canLoadMore = this.responses.attempts.length < responses.totalattempts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.setItems((<EntryItem[]> this.responses.attempts).concat(this.anonResponses.attempts));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    resetItems(): void {
 | 
				
			||||||
 | 
					        super.resetItems();
 | 
				
			||||||
 | 
					        this.responses.total = 0;
 | 
				
			||||||
 | 
					        this.responses.attempts = [];
 | 
				
			||||||
 | 
					        this.anonResponses.total = 0;
 | 
				
			||||||
 | 
					        this.anonResponses.attempts = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getItemPath(entry: EntryItem): string {
 | 
				
			||||||
 | 
					        return `attempt/${entry.id}`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AddonModFeedbackResponses = {
 | 
				
			||||||
 | 
					    attempts: AddonModFeedbackWSAttempt[];
 | 
				
			||||||
 | 
					    total: number;
 | 
				
			||||||
 | 
					    canLoadMore: boolean;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AddonModFeedbackAnonResponses = {
 | 
				
			||||||
 | 
					    attempts: AddonModFeedbackWSAnonAttempt[];
 | 
				
			||||||
 | 
					    total: number;
 | 
				
			||||||
 | 
					    canLoadMore: boolean;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										63
									
								
								src/addons/mod/feedback/services/database/feedback.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/addons/mod/feedback/services/database/feedback.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 { CoreSiteSchema } from '@services/sites';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Database variables for AddonModFeedbackOfflineProvider.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const FEEDBACK_TABLE_NAME = 'addon_mod_feedback_answers';
 | 
				
			||||||
 | 
					export const OFFLINE_SITE_SCHEMA: CoreSiteSchema = {
 | 
				
			||||||
 | 
					    name: 'AddonModFeedbackOfflineProvider',
 | 
				
			||||||
 | 
					    version: 1,
 | 
				
			||||||
 | 
					    tables: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            name: FEEDBACK_TABLE_NAME,
 | 
				
			||||||
 | 
					            columns: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    name: 'feedbackid',
 | 
				
			||||||
 | 
					                    type: 'INTEGER',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    name: 'page',
 | 
				
			||||||
 | 
					                    type: 'INTEGER',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    name: 'courseid',
 | 
				
			||||||
 | 
					                    type: 'INTEGER',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    name: 'responses',
 | 
				
			||||||
 | 
					                    type: 'TEXT',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    name: 'timemodified',
 | 
				
			||||||
 | 
					                    type: 'INTEGER',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            primaryKeys: ['feedbackid', 'page'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Response data.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackResponseDBRecord = {
 | 
				
			||||||
 | 
					    feedbackid: number;
 | 
				
			||||||
 | 
					    page: number;
 | 
				
			||||||
 | 
					    courseid: number;
 | 
				
			||||||
 | 
					    responses: string;
 | 
				
			||||||
 | 
					    timemodified: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										585
									
								
								src/addons/mod/feedback/services/feedback-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										585
									
								
								src/addons/mod/feedback/services/feedback-helper.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,585 @@
 | 
				
			|||||||
 | 
					// (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 { CoreCourse } from '@features/course/services/course';
 | 
				
			||||||
 | 
					import { CoreUser } from '@features/user/services/user';
 | 
				
			||||||
 | 
					import { CoreNavigator } from '@services/navigator';
 | 
				
			||||||
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
 | 
					import { CoreTimeUtils } from '@services/utils/time';
 | 
				
			||||||
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    AddonModFeedback,
 | 
				
			||||||
 | 
					    AddonModFeedbackGetNonRespondentsWSResponse,
 | 
				
			||||||
 | 
					    AddonModFeedbackGetResponsesAnalysisWSResponse,
 | 
				
			||||||
 | 
					    AddonModFeedbackGroupPaginatedOptions,
 | 
				
			||||||
 | 
					    AddonModFeedbackItem,
 | 
				
			||||||
 | 
					    AddonModFeedbackProvider,
 | 
				
			||||||
 | 
					    AddonModFeedbackResponseValue,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSAttempt,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSNonRespondent,
 | 
				
			||||||
 | 
					} from './feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackModuleHandlerService } from './handlers/module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MODE_RESPONSETIME = 1;
 | 
				
			||||||
 | 
					const MODE_COURSE = 2;
 | 
				
			||||||
 | 
					const MODE_CATEGORY = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Service that provides helper functions for feedbacks.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackHelperProvider {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Retrieves a list of students who didn't submit the feedback with extra info.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param options Other options.
 | 
				
			||||||
 | 
					     * @return Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async getNonRespondents(
 | 
				
			||||||
 | 
					        feedbackId: number,
 | 
				
			||||||
 | 
					        options: AddonModFeedbackGroupPaginatedOptions = {},
 | 
				
			||||||
 | 
					    ): Promise<AddonModFeedbackGetNonRespondents> {
 | 
				
			||||||
 | 
					        const responses: AddonModFeedbackGetNonRespondents = await AddonModFeedback.getNonRespondents(feedbackId, options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        responses.users = await this.addImageProfile(responses.users);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return responses;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get page items responses to be sent.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param items Items where the values are.
 | 
				
			||||||
 | 
					     * @return Responses object to be sent.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getPageItemsResponses(items: AddonModFeedbackFormItem[]): Record<string, AddonModFeedbackResponseValue> {
 | 
				
			||||||
 | 
					        const responses: Record<string, AddonModFeedbackResponseValue> = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        items.forEach((itemData) => {
 | 
				
			||||||
 | 
					            let answered = false;
 | 
				
			||||||
 | 
					            itemData.hasError = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (itemData.typ == 'captcha') {
 | 
				
			||||||
 | 
					                const value = itemData.value || '';
 | 
				
			||||||
 | 
					                const name = itemData.typ + '_' + itemData.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                answered = !!value;
 | 
				
			||||||
 | 
					                responses[name] = 1;
 | 
				
			||||||
 | 
					                responses['g-recaptcha-response'] = value;
 | 
				
			||||||
 | 
					                responses['recaptcha_element'] = 'dummyvalue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (itemData.required && !answered) {
 | 
				
			||||||
 | 
					                    // Check if it has any value.
 | 
				
			||||||
 | 
					                    itemData.isEmpty = true;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    itemData.isEmpty = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else if (itemData.hasvalue) {
 | 
				
			||||||
 | 
					                let name: string;
 | 
				
			||||||
 | 
					                let value: AddonModFeedbackResponseValue;
 | 
				
			||||||
 | 
					                const nameTemp = itemData.typ + '_' + itemData.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (this.isMultiChoiceItem(itemData) && itemData.subtype == 'c') {
 | 
				
			||||||
 | 
					                    name = nameTemp + '[0]';
 | 
				
			||||||
 | 
					                    responses[name] = 0;
 | 
				
			||||||
 | 
					                    itemData.choices.forEach((choice, index) => {
 | 
				
			||||||
 | 
					                        name = nameTemp + '[' + (index + 1) + ']';
 | 
				
			||||||
 | 
					                        value = choice.checked ? choice.value : 0;
 | 
				
			||||||
 | 
					                        if (!answered && value) {
 | 
				
			||||||
 | 
					                            answered = true;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        responses[name] = value;
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if (this.isMultiChoiceItem(itemData) && itemData.subtype != 'r') {
 | 
				
			||||||
 | 
					                        name = nameTemp + '[0]';
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        name = nameTemp;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (itemData.typ == 'multichoice' || itemData.typ == 'multichoicerated') {
 | 
				
			||||||
 | 
					                        value = itemData.value || 0;
 | 
				
			||||||
 | 
					                    } else if (this.isNumericItem(itemData)) {
 | 
				
			||||||
 | 
					                        value = itemData.value || itemData.value  == 0 ? itemData.value : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (value != '') {
 | 
				
			||||||
 | 
					                            if ((itemData.rangefrom != '' && value < itemData.rangefrom) ||
 | 
				
			||||||
 | 
					                                    (itemData.rangeto != '' && value > itemData.rangeto)) {
 | 
				
			||||||
 | 
					                                itemData.hasError = true;
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        value = itemData.value || itemData.value  == 0 ? itemData.value : '';
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    answered = !!value;
 | 
				
			||||||
 | 
					                    responses[name] = value;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (itemData.required && !answered) {
 | 
				
			||||||
 | 
					                    // Check if it has any value.
 | 
				
			||||||
 | 
					                    itemData.isEmpty = true;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    itemData.isEmpty = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return responses;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns the feedback user responses with extra info.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param options Other options.
 | 
				
			||||||
 | 
					     * @return Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async getResponsesAnalysis(
 | 
				
			||||||
 | 
					        feedbackId: number,
 | 
				
			||||||
 | 
					        options: AddonModFeedbackGroupPaginatedOptions = {},
 | 
				
			||||||
 | 
					    ): Promise<AddonModFeedbackResponsesAnalysis> {
 | 
				
			||||||
 | 
					        const responses: AddonModFeedbackResponsesAnalysis = await AddonModFeedback.getResponsesAnalysis(feedbackId, options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        responses.attempts = await this.addImageProfile(responses.attempts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return responses;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Handle a show entries link.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param params URL params.
 | 
				
			||||||
 | 
					     * @param siteId Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async handleShowEntriesLink(params: Record<string, string>, siteId?: string): Promise<void> {
 | 
				
			||||||
 | 
					        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const modal = await CoreDomUtils.showModalLoading();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const module = await CoreCourse.getModuleBasicInfo(Number(params.id), siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (typeof params.showcompleted == 'undefined') {
 | 
				
			||||||
 | 
					                // Param showcompleted not defined. Show entry list.
 | 
				
			||||||
 | 
					                await CoreNavigator.navigateToSitePath(
 | 
				
			||||||
 | 
					                    AddonModFeedbackModuleHandlerService.PAGE_NAME + `/${module.course}/${module.id}/respondents`,
 | 
				
			||||||
 | 
					                    { siteId },
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const attempt = await AddonModFeedback.getAttempt(module.instance, Number(params.showcompleted), {
 | 
				
			||||||
 | 
					                cmId: module.id,
 | 
				
			||||||
 | 
					                readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
 | 
				
			||||||
 | 
					                siteId,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await CoreNavigator.navigateToSitePath(
 | 
				
			||||||
 | 
					                AddonModFeedbackModuleHandlerService.PAGE_NAME + `/${module.course}/${module.id}/attempt/${attempt.id}`,
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    params: {
 | 
				
			||||||
 | 
					                        feedbackId: module.instance,
 | 
				
			||||||
 | 
					                        attempt: attempt,
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    siteId,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            CoreDomUtils.showErrorModalDefault(error, 'Error opening link.');
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            modal.dismiss();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Add Image profile url field on some entries.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param entries Entries array to get profile from.
 | 
				
			||||||
 | 
					     * @return Returns the same array with the profileimageurl added if found.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async addImageProfile(entries: AddonModFeedbackWSAttempt[]): Promise<AddonModFeedbackAttempt[]>;
 | 
				
			||||||
 | 
					    protected async addImageProfile(entries: AddonModFeedbackWSNonRespondent[]): Promise<AddonModFeedbackNonRespondent[]>;
 | 
				
			||||||
 | 
					    protected async addImageProfile(
 | 
				
			||||||
 | 
					        entries: (AddonModFeedbackWSAttempt | AddonModFeedbackWSNonRespondent)[],
 | 
				
			||||||
 | 
					    ): Promise<(AddonModFeedbackAttempt | AddonModFeedbackNonRespondent)[]> {
 | 
				
			||||||
 | 
					        return await Promise.all(entries.map(async (entry: AddonModFeedbackAttempt | AddonModFeedbackNonRespondent) => {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const user = await CoreUser.getProfile(entry.userid, entry.courseid, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                entry.profileimageurl = user.profileimageurl;
 | 
				
			||||||
 | 
					            } catch {
 | 
				
			||||||
 | 
					                // Error getting profile, resolve promise without adding any extra data.
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return entry;
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Helper funtion for item type Label.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param item Item to process.
 | 
				
			||||||
 | 
					     * @return Item processed to show form.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getItemFormLabel(item: AddonModFeedbackItem): AddonModFeedbackFormBasicItem {
 | 
				
			||||||
 | 
					        item.name = '';
 | 
				
			||||||
 | 
					        item.presentation = CoreTextUtils.replacePluginfileUrls(item.presentation, item.itemfiles);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Object.assign(item, {
 | 
				
			||||||
 | 
					            templateName: 'label',
 | 
				
			||||||
 | 
					            value: '',
 | 
				
			||||||
 | 
					            hasTextInput: false,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Helper funtion for item type Info.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param item Item to process.
 | 
				
			||||||
 | 
					     * @return Item processed to show form.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getItemFormInfo(item: AddonModFeedbackItem): AddonModFeedbackFormBasicItem | undefined {
 | 
				
			||||||
 | 
					        const formItem: AddonModFeedbackFormBasicItem = Object.assign(item, {
 | 
				
			||||||
 | 
					            templateName: 'label',
 | 
				
			||||||
 | 
					            value: '',
 | 
				
			||||||
 | 
					            hasTextInput: false,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const type = parseInt(formItem.presentation, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (type == MODE_COURSE || type == MODE_CATEGORY) {
 | 
				
			||||||
 | 
					            formItem.presentation = formItem.otherdata;
 | 
				
			||||||
 | 
					            formItem.value = typeof formItem.rawValue != 'undefined' ? formItem.rawValue : formItem.otherdata;
 | 
				
			||||||
 | 
					        } else if (type == MODE_RESPONSETIME) {
 | 
				
			||||||
 | 
					            formItem.value = '__CURRENT__TIMESTAMP__';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const rawValue = Number(formItem.rawValue);
 | 
				
			||||||
 | 
					            const tempValue = isNaN(rawValue) ? Date.now() : rawValue * 1000;
 | 
				
			||||||
 | 
					            formItem.presentation = CoreTimeUtils.userDate(tempValue);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            // Errors on item, return false.
 | 
				
			||||||
 | 
					            return undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return formItem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Helper funtion for item type Numeric.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param item Item to process.
 | 
				
			||||||
 | 
					     * @return Item processed to show form.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getItemFormNumeric(item: AddonModFeedbackItem): AddonModFeedbackNumericItem {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const range = item.presentation.split(AddonModFeedbackProvider.LINE_SEP) || [];
 | 
				
			||||||
 | 
					        const rangeFrom = range.length > 0 ? parseInt(range[0], 10) : undefined;
 | 
				
			||||||
 | 
					        const rangeTo = range.length > 1 ? parseInt(range[1], 10) : undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const formItem: AddonModFeedbackNumericItem = Object.assign(item, {
 | 
				
			||||||
 | 
					            templateName: 'numeric',
 | 
				
			||||||
 | 
					            value: typeof item.rawValue != 'undefined' ? Number(item.rawValue) : '',
 | 
				
			||||||
 | 
					            rangefrom: typeof rangeFrom == 'number' && !isNaN(rangeFrom) ? range[0] : '',
 | 
				
			||||||
 | 
					            rangeto: typeof rangeTo == 'number' && !isNaN(rangeTo) ? rangeTo : '',
 | 
				
			||||||
 | 
					            hasTextInput: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        formItem.postfix = this.getNumericBoundariesForDisplay(formItem.rangefrom, formItem.rangeto);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return formItem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Helper funtion for item type Text field.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param item Item to process.
 | 
				
			||||||
 | 
					     * @return Item processed to show form.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getItemFormTextfield(item: AddonModFeedbackItem): AddonModFeedbackTextItem {
 | 
				
			||||||
 | 
					        return Object.assign(item, {
 | 
				
			||||||
 | 
					            templateName: 'textfield',
 | 
				
			||||||
 | 
					            length: Number(item.presentation.split(AddonModFeedbackProvider.LINE_SEP)[1]) || 255,
 | 
				
			||||||
 | 
					            value: typeof item.rawValue != 'undefined' ? item.rawValue : '',
 | 
				
			||||||
 | 
					            hasTextInput: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Helper funtion for item type Textarea.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param item Item to process.
 | 
				
			||||||
 | 
					     * @return Item processed to show form.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getItemFormTextarea(item: AddonModFeedbackItem): AddonModFeedbackFormBasicItem {
 | 
				
			||||||
 | 
					        return Object.assign(item, {
 | 
				
			||||||
 | 
					            templateName: 'textarea',
 | 
				
			||||||
 | 
					            value: typeof item.rawValue != 'undefined' ? item.rawValue : '',
 | 
				
			||||||
 | 
					            hasTextInput: true,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Helper funtion for item type Multichoice.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param item Item to process.
 | 
				
			||||||
 | 
					     * @return Item processed to show form.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getItemFormMultichoice(item: AddonModFeedbackItem): AddonModFeedbackMultichoiceItem {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let parts = item.presentation.split(AddonModFeedbackProvider.MULTICHOICE_TYPE_SEP) || [];
 | 
				
			||||||
 | 
					        const subType = parts.length > 0 && parts[0] ? parts[0] : 'r';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const formItem: AddonModFeedbackMultichoiceItem = Object.assign(item, {
 | 
				
			||||||
 | 
					            templateName: 'multichoice-' + subType,
 | 
				
			||||||
 | 
					            subtype: subType,
 | 
				
			||||||
 | 
					            value: '',
 | 
				
			||||||
 | 
					            choices: [],
 | 
				
			||||||
 | 
					            hasTextInput: false,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formItem.presentation = parts.length > 1 ? parts[1] : '';
 | 
				
			||||||
 | 
					        if (formItem.subtype != 'd') {
 | 
				
			||||||
 | 
					            parts = formItem.presentation.split(AddonModFeedbackProvider.MULTICHOICE_ADJUST_SEP) || [];
 | 
				
			||||||
 | 
					            formItem.presentation = parts.length > 0 ? parts[0] : '';
 | 
				
			||||||
 | 
					            // Horizontal are not supported right now. item.horizontal = parts.length > 1 && !!parts[1];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const choices = formItem.presentation.split(AddonModFeedbackProvider.LINE_SEP) || [];
 | 
				
			||||||
 | 
					        formItem.choices = choices.map((choice, index) => {
 | 
				
			||||||
 | 
					            const weightValue = choice.split(AddonModFeedbackProvider.MULTICHOICERATED_VALUE_SEP) || [''];
 | 
				
			||||||
 | 
					            choice = weightValue.length == 1 ? weightValue[0] : '(' + weightValue[0] + ') ' + weightValue[1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return { value: index + 1, label: choice };
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (formItem.subtype === 'r' && formItem.options.search(AddonModFeedbackProvider.MULTICHOICE_HIDENOSELECT) == -1) {
 | 
				
			||||||
 | 
					            formItem.choices.unshift({ value: 0, label: Translate.instant('addon.mod_feedback.not_selected') });
 | 
				
			||||||
 | 
					            formItem.value = typeof formItem.rawValue != 'undefined' ? Number(formItem.rawValue) : 0;
 | 
				
			||||||
 | 
					        } else if (formItem.subtype === 'd') {
 | 
				
			||||||
 | 
					            formItem.choices.unshift({ value: 0, label: '' });
 | 
				
			||||||
 | 
					            formItem.value = typeof formItem.rawValue != 'undefined' ? Number(formItem.rawValue) : 0;
 | 
				
			||||||
 | 
					        } else if (formItem.subtype === 'c') {
 | 
				
			||||||
 | 
					            if (typeof formItem.rawValue != 'undefined') {
 | 
				
			||||||
 | 
					                formItem.rawValue = String(formItem.rawValue);
 | 
				
			||||||
 | 
					                const values = formItem.rawValue.split(AddonModFeedbackProvider.LINE_SEP);
 | 
				
			||||||
 | 
					                formItem.choices.forEach((choice) => {
 | 
				
			||||||
 | 
					                    for (const x in values) {
 | 
				
			||||||
 | 
					                        if (choice.value == Number(values[x])) {
 | 
				
			||||||
 | 
					                            choice.checked = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            return;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            formItem.value = typeof formItem.rawValue != 'undefined' ? Number(formItem.rawValue) : '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return formItem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Helper funtion for item type Captcha.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param item Item to process.
 | 
				
			||||||
 | 
					     * @return Item processed to show form.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getItemFormCaptcha(item: AddonModFeedbackItem): AddonModFeedbackCaptchaItem {
 | 
				
			||||||
 | 
					        const formItem: AddonModFeedbackCaptchaItem = Object.assign(item, {
 | 
				
			||||||
 | 
					            templateName: 'captcha',
 | 
				
			||||||
 | 
					            value: '',
 | 
				
			||||||
 | 
					            hasTextInput: false,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const data = <string[]> CoreTextUtils.parseJSON(item.otherdata);
 | 
				
			||||||
 | 
					        if (data && data.length > 3) {
 | 
				
			||||||
 | 
					            formItem.captcha = {
 | 
				
			||||||
 | 
					                recaptchapublickey: data[3],
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return formItem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Process and returns item to print form.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param item Item to process.
 | 
				
			||||||
 | 
					     * @param preview Previewing options.
 | 
				
			||||||
 | 
					     * @return Item processed to show form.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getItemForm(item: AddonModFeedbackItem, preview: boolean): AddonModFeedbackFormItem | undefined {
 | 
				
			||||||
 | 
					        switch (item.typ) {
 | 
				
			||||||
 | 
					            case 'label':
 | 
				
			||||||
 | 
					                return this.getItemFormLabel(item);
 | 
				
			||||||
 | 
					            case 'info':
 | 
				
			||||||
 | 
					                return this.getItemFormInfo(item);
 | 
				
			||||||
 | 
					            case 'numeric':
 | 
				
			||||||
 | 
					                return this.getItemFormNumeric(item);
 | 
				
			||||||
 | 
					            case 'textfield':
 | 
				
			||||||
 | 
					                return this.getItemFormTextfield(item);
 | 
				
			||||||
 | 
					            case 'textarea':
 | 
				
			||||||
 | 
					                return this.getItemFormTextarea(item);
 | 
				
			||||||
 | 
					            case 'multichoice':
 | 
				
			||||||
 | 
					                return this.getItemFormMultichoice(item);
 | 
				
			||||||
 | 
					            case 'multichoicerated':
 | 
				
			||||||
 | 
					                return this.getItemFormMultichoice(item);
 | 
				
			||||||
 | 
					            case 'pagebreak':
 | 
				
			||||||
 | 
					                if (!preview) {
 | 
				
			||||||
 | 
					                    // Pagebreaks are only used on preview.
 | 
				
			||||||
 | 
					                    return undefined;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case 'captcha':
 | 
				
			||||||
 | 
					                // Captcha is not supported right now. However label will be shown.
 | 
				
			||||||
 | 
					                return this.getItemFormCaptcha(item);
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                return undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns human-readable boundaries (min - max).
 | 
				
			||||||
 | 
					     * Based on Moodle's get_boundaries_for_display.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param rangeFrom Range from.
 | 
				
			||||||
 | 
					     * @param rangeTo Range to.
 | 
				
			||||||
 | 
					     * @return Human-readable boundaries.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getNumericBoundariesForDisplay(rangeFrom: number | string, rangeTo: number | string): string {
 | 
				
			||||||
 | 
					        const rangeFromSet = typeof rangeFrom == 'number';
 | 
				
			||||||
 | 
					        const rangeToSet = typeof rangeTo == 'number';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!rangeFromSet && rangeToSet) {
 | 
				
			||||||
 | 
					            return ' (' + Translate.instant('addon.mod_feedback.maximal') + ': ' + CoreUtils.formatFloat(rangeTo) + ')';
 | 
				
			||||||
 | 
					        } else if (rangeFromSet && !rangeToSet) {
 | 
				
			||||||
 | 
					            return ' (' + Translate.instant('addon.mod_feedback.minimal') + ': ' + CoreUtils.formatFloat(rangeFrom) + ')';
 | 
				
			||||||
 | 
					        } else if (!rangeFromSet && !rangeToSet) {
 | 
				
			||||||
 | 
					            return '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ' (' + CoreUtils.formatFloat(rangeFrom) + ' - ' + CoreUtils.formatFloat(rangeTo) + ')';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check if a form item is multichoice.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param item Item.
 | 
				
			||||||
 | 
					     * @return Whether item is multichoice.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected isMultiChoiceItem(item: AddonModFeedbackFormItem): item is AddonModFeedbackMultichoiceItem {
 | 
				
			||||||
 | 
					        return item.typ == 'multichoice';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check if a form item is numeric.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param item Item.
 | 
				
			||||||
 | 
					     * @return Whether item is numeric.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected isNumericItem(item: AddonModFeedbackFormItem): item is AddonModFeedbackNumericItem {
 | 
				
			||||||
 | 
					        return item.typ == 'numeric';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackHelper = makeSingleton(AddonModFeedbackHelperProvider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Attempt with some calculated data.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackAttempt = AddonModFeedbackWSAttempt & {
 | 
				
			||||||
 | 
					    profileimageurl?: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Non respondent with some calculated data.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackNonRespondent = AddonModFeedbackWSNonRespondent & {
 | 
				
			||||||
 | 
					    profileimageurl?: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Non respondents with some calculated data.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackResponsesAnalysis = Omit<AddonModFeedbackGetResponsesAnalysisWSResponse, 'attempts'> & {
 | 
				
			||||||
 | 
					    attempts: AddonModFeedbackAttempt[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Non respondents with some calculated data.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackGetNonRespondents = Omit<AddonModFeedbackGetNonRespondentsWSResponse, 'users'> & {
 | 
				
			||||||
 | 
					    users: AddonModFeedbackNonRespondent[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Item with form data.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackFormItem =
 | 
				
			||||||
 | 
					    AddonModFeedbackFormBasicItem | AddonModFeedbackNumericItem | AddonModFeedbackTextItem | AddonModFeedbackMultichoiceItem |
 | 
				
			||||||
 | 
					    AddonModFeedbackCaptchaItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Common calculated data for all form items.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackFormBasicItem = AddonModFeedbackItem & {
 | 
				
			||||||
 | 
					    templateName: string;
 | 
				
			||||||
 | 
					    value: AddonModFeedbackResponseValue;
 | 
				
			||||||
 | 
					    hasTextInput: boolean;
 | 
				
			||||||
 | 
					    isEmpty?: boolean;
 | 
				
			||||||
 | 
					    hasError?: boolean;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Numeric item.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackNumericItem = AddonModFeedbackFormBasicItem & {
 | 
				
			||||||
 | 
					    rangefrom: number | string;
 | 
				
			||||||
 | 
					    rangeto: number | string;
 | 
				
			||||||
 | 
					    postfix?: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Text item.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackTextItem = AddonModFeedbackFormBasicItem & {
 | 
				
			||||||
 | 
					    length: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Multichoice item.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackMultichoiceItem = AddonModFeedbackFormBasicItem & {
 | 
				
			||||||
 | 
					    subtype: string;
 | 
				
			||||||
 | 
					    choices: { value: number; label: string; checked?: boolean }[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Captcha item.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackCaptchaItem = AddonModFeedbackFormBasicItem & {
 | 
				
			||||||
 | 
					    captcha?: {
 | 
				
			||||||
 | 
					        recaptchapublickey: string;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										162
									
								
								src/addons/mod/feedback/services/feedback-offline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								src/addons/mod/feedback/services/feedback-offline.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,162 @@
 | 
				
			|||||||
 | 
					// (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 { CoreSites } from '@services/sites';
 | 
				
			||||||
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
 | 
					import { CoreTimeUtils } from '@services/utils/time';
 | 
				
			||||||
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
 | 
					import { AddonModFeedbackResponseDBRecord, FEEDBACK_TABLE_NAME } from './database/feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackResponseValue } from './feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Service to handle offline feedback.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackOfflineProvider {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Delete the stored for a certain feedback page.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param page Page of the form to delete responses from.
 | 
				
			||||||
 | 
					     * @param siteId Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return Promise resolved if deleted, rejected if failure.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async deleteFeedbackPageResponses(feedbackId: number, page: number, siteId?: string): Promise<void> {
 | 
				
			||||||
 | 
					        const site = await CoreSites.getSite(siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await site.getDb().deleteRecords(FEEDBACK_TABLE_NAME, <Partial<AddonModFeedbackResponseDBRecord>> {
 | 
				
			||||||
 | 
					            feedbackid: feedbackId,
 | 
				
			||||||
 | 
					            page: page,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get all the stored feedback responses data from all the feedback.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param siteId Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return Promise resolved with entries.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async getAllFeedbackResponses(siteId?: string): Promise<AddonModFeedbackOfflineResponse[]> {
 | 
				
			||||||
 | 
					        const site = await CoreSites.getSite(siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const entries = await site.getDb().getAllRecords<AddonModFeedbackResponseDBRecord>(FEEDBACK_TABLE_NAME);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return entries.map(entry => this.parseResponse(entry));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get all the stored responses from a certain feedback.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param siteId Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return Promise resolved with responses.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async getFeedbackResponses(feedbackId: number, siteId?: string): Promise<AddonModFeedbackOfflineResponse[]> {
 | 
				
			||||||
 | 
					        const site = await CoreSites.getSite(siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const entries = await site.getDb().getRecords<AddonModFeedbackResponseDBRecord>(FEEDBACK_TABLE_NAME, {
 | 
				
			||||||
 | 
					            feedbackid: feedbackId,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return entries.map(entry => this.parseResponse(entry));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the stored responses for a certain feedback page.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param page Page of the form to get responses from.
 | 
				
			||||||
 | 
					     * @param siteId Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return Promise resolved with responses.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async getFeedbackPageResponses(feedbackId: number, page: number, siteId?: string): Promise<AddonModFeedbackOfflineResponse> {
 | 
				
			||||||
 | 
					        const site = await CoreSites.getSite(siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const conditions: Partial<AddonModFeedbackResponseDBRecord> = {
 | 
				
			||||||
 | 
					            feedbackid: feedbackId,
 | 
				
			||||||
 | 
					            page: page,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const entry = await site.getDb().getRecord<AddonModFeedbackResponseDBRecord>(FEEDBACK_TABLE_NAME, conditions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.parseResponse(entry);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get if the feedback have something to be synced.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param siteId Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return Promise resolved with true if the feedback have something to be synced.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async hasFeedbackOfflineData(feedbackId: number, siteId?: string): Promise<boolean> {
 | 
				
			||||||
 | 
					        const responses = await this.getFeedbackResponses(feedbackId, siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return !!responses.length;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Parse "options" and "attachments" columns of a fetched record.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param records Record object
 | 
				
			||||||
 | 
					     * @return Record object with columns parsed.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected parseResponse(record: AddonModFeedbackResponseDBRecord): AddonModFeedbackOfflineResponse {
 | 
				
			||||||
 | 
					        return Object.assign(record, {
 | 
				
			||||||
 | 
					            responses: <Record<string, AddonModFeedbackResponseValue>> CoreTextUtils.parseJSON(record.responses),
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Save page responses to be sent later.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param page The page being processed.
 | 
				
			||||||
 | 
					     * @param responses The data to be processed the key is the field name (usually type[index]_id)
 | 
				
			||||||
 | 
					     * @param courseId Course ID the feedback belongs to.
 | 
				
			||||||
 | 
					     * @param siteId Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return Promise resolved if stored, rejected if failure.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async saveResponses(
 | 
				
			||||||
 | 
					        feedbackId: number,
 | 
				
			||||||
 | 
					        page: number,
 | 
				
			||||||
 | 
					        responses: Record<string, AddonModFeedbackResponseValue>,
 | 
				
			||||||
 | 
					        courseId: number,
 | 
				
			||||||
 | 
					        siteId?: string,
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        const site = await CoreSites.getSite(siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const entry: AddonModFeedbackResponseDBRecord = {
 | 
				
			||||||
 | 
					            feedbackid: feedbackId,
 | 
				
			||||||
 | 
					            page: page,
 | 
				
			||||||
 | 
					            courseid: courseId,
 | 
				
			||||||
 | 
					            responses: JSON.stringify(responses),
 | 
				
			||||||
 | 
					            timemodified: CoreTimeUtils.timestamp(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await site.getDb().insertRecord(FEEDBACK_TABLE_NAME, entry);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackOffline = makeSingleton(AddonModFeedbackOfflineProvider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Feedback offline response with parsed data.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackOfflineResponse = Omit<AddonModFeedbackResponseDBRecord, 'responses'> & {
 | 
				
			||||||
 | 
					    responses: Record<string, AddonModFeedbackResponseValue>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										305
									
								
								src/addons/mod/feedback/services/feedback-sync.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								src/addons/mod/feedback/services/feedback-sync.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,305 @@
 | 
				
			|||||||
 | 
					// (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 { CoreSyncBlockedError } from '@classes/base-sync';
 | 
				
			||||||
 | 
					import { CoreNetworkError } from '@classes/errors/network-error';
 | 
				
			||||||
 | 
					import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync';
 | 
				
			||||||
 | 
					import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
 | 
				
			||||||
 | 
					import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
				
			||||||
 | 
					import { CoreApp } from '@services/app';
 | 
				
			||||||
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
 | 
					import { CoreSync } from '@services/sync';
 | 
				
			||||||
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
 | 
					import { CoreEvents } from '@singletons/events';
 | 
				
			||||||
 | 
					import { AddonModFeedback, AddonModFeedbackProvider, AddonModFeedbackWSFeedback } from './feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackOffline, AddonModFeedbackOfflineResponse } from './feedback-offline';
 | 
				
			||||||
 | 
					import { AddonModFeedbackPrefetchHandler, AddonModFeedbackPrefetchHandlerService } from './handlers/prefetch';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Service to sync feedbacks.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProvider<AddonModFeedbackSyncResult> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static readonly AUTO_SYNCED = 'addon_mod_feedback_autom_synced';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected componentTranslatableString = 'feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super('AddonModFeedbackSyncProvider');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    prefetchAfterUpdate(
 | 
				
			||||||
 | 
					        prefetchHandler: AddonModFeedbackPrefetchHandlerService,
 | 
				
			||||||
 | 
					        module: CoreCourseAnyModuleData,
 | 
				
			||||||
 | 
					        courseId: number,
 | 
				
			||||||
 | 
					        regex?: RegExp,
 | 
				
			||||||
 | 
					        siteId?: string,
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        regex = regex || /^.*files$|^timers/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return super.prefetchAfterUpdate(prefetchHandler, module, courseId, regex, siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Try to synchronize all the feedbacks 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.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    syncAllFeedbacks(siteId?: string, force?: boolean): Promise<void> {
 | 
				
			||||||
 | 
					        return this.syncOnSites('all feedbacks', this.syncAllFeedbacksFunc.bind(this, !!force), siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sync all pending feedbacks 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 syncAllFeedbacksFunc(force: boolean, siteId?: string): Promise<void> {
 | 
				
			||||||
 | 
					        // Sync all new responses.
 | 
				
			||||||
 | 
					        const responses = await AddonModFeedbackOffline.getAllFeedbackResponses(siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Do not sync same feedback twice.
 | 
				
			||||||
 | 
					        const treated: Record<number, boolean> = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await Promise.all(responses.map(async (response) => {
 | 
				
			||||||
 | 
					            if (treated[response.feedbackid]) {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            treated[response.feedbackid] = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const result = force ?
 | 
				
			||||||
 | 
					                await this.syncFeedback(response.feedbackid, siteId) :
 | 
				
			||||||
 | 
					                await this.syncFeedbackIfNeeded(response.feedbackid, siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (result?.updated) {
 | 
				
			||||||
 | 
					                // Sync successful, send event.
 | 
				
			||||||
 | 
					                CoreEvents.trigger(AddonModFeedbackSyncProvider.AUTO_SYNCED, {
 | 
				
			||||||
 | 
					                    feedbackId: response.feedbackid,
 | 
				
			||||||
 | 
					                    warnings: result.warnings,
 | 
				
			||||||
 | 
					                }, siteId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sync a feedback only if a certain time has passed since the last time.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param siteId Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return Promise resolved when the feedback is synced or if it doesn't need to be synced.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async syncFeedbackIfNeeded(feedbackId: number, siteId?: string): Promise<AddonModFeedbackSyncResult | undefined> {
 | 
				
			||||||
 | 
					        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const needed = await this.isSyncNeeded(feedbackId, siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (needed) {
 | 
				
			||||||
 | 
					            return this.syncFeedback(feedbackId, siteId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Synchronize all offline responses of a feedback.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedbackId Feedback ID to be synced.
 | 
				
			||||||
 | 
					     * @param siteId Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return Promise resolved if sync is successful, rejected otherwise.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    syncFeedback(feedbackId: number, siteId?: string): Promise<AddonModFeedbackSyncResult> {
 | 
				
			||||||
 | 
					        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.isSyncing(feedbackId, siteId)) {
 | 
				
			||||||
 | 
					            // There's already a sync ongoing for this feedback, return the promise.
 | 
				
			||||||
 | 
					            return this.getOngoingSync(feedbackId, siteId)!;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Verify that feedback isn't blocked.
 | 
				
			||||||
 | 
					        if (CoreSync.isBlocked(AddonModFeedbackProvider.COMPONENT, feedbackId, siteId)) {
 | 
				
			||||||
 | 
					            this.logger.debug(`Cannot sync feedback '${feedbackId}' because it is blocked.`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            throw new CoreSyncBlockedError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate }));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.logger.debug(`Try to sync feedback '${feedbackId}' in site ${siteId}'`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.addOngoingSync(feedbackId, this.performSyncFeedback(feedbackId, siteId), siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Perform the feedback sync.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param siteId Site ID.
 | 
				
			||||||
 | 
					     * @return Promise resolved in success.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async performSyncFeedback(feedbackId: number, siteId: string): Promise<AddonModFeedbackSyncResult> {
 | 
				
			||||||
 | 
					        const result: AddonModFeedbackSyncResult = {
 | 
				
			||||||
 | 
					            warnings: [],
 | 
				
			||||||
 | 
					            updated: false,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Sync offline logs.
 | 
				
			||||||
 | 
					        await CoreUtils.ignoreErrors(CoreCourseLogHelper.syncActivity(AddonModFeedbackProvider.COMPONENT, feedbackId, siteId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Get offline responses to be sent.
 | 
				
			||||||
 | 
					        const responses = await CoreUtils.ignoreErrors(AddonModFeedbackOffline.getFeedbackResponses(feedbackId, siteId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!responses || !responses.length) {
 | 
				
			||||||
 | 
					            // Nothing to sync.
 | 
				
			||||||
 | 
					            await CoreUtils.ignoreErrors(this.setSyncTime(feedbackId, siteId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!CoreApp.isOnline()) {
 | 
				
			||||||
 | 
					            // Cannot sync in offline.
 | 
				
			||||||
 | 
					            throw new CoreNetworkError();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const courseId = responses[0].courseid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const feedback = await AddonModFeedback.getFeedbackById(courseId, feedbackId, { siteId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!feedback.multiple_submit) {
 | 
				
			||||||
 | 
					            // If it does not admit multiple submits, check if it is completed to know if we can submit.
 | 
				
			||||||
 | 
					            const isCompleted = await AddonModFeedback.isCompleted(feedbackId, { cmId: feedback.coursemodule, siteId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (isCompleted) {
 | 
				
			||||||
 | 
					                // Cannot submit again, delete resposes.
 | 
				
			||||||
 | 
					                await Promise.all(responses.map((data) =>
 | 
				
			||||||
 | 
					                    AddonModFeedbackOffline.deleteFeedbackPageResponses(feedbackId, data.page, siteId)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                result.updated = true;
 | 
				
			||||||
 | 
					                this.addOfflineDataDeletedWarning(
 | 
				
			||||||
 | 
					                    result.warnings,
 | 
				
			||||||
 | 
					                    feedback.name,
 | 
				
			||||||
 | 
					                    Translate.instant('addon.mod_feedback.this_feedback_is_already_submitted'),
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await CoreUtils.ignoreErrors(this.setSyncTime(feedbackId, siteId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return result;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const timemodified = await AddonModFeedback.getCurrentCompletedTimeModified(feedbackId, {
 | 
				
			||||||
 | 
					            readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
 | 
				
			||||||
 | 
					            siteId,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        // Sort by page.
 | 
				
			||||||
 | 
					        responses.sort((a, b) => a.page - b.page);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const orderedData = responses.map((data) => ({
 | 
				
			||||||
 | 
					            function: this.processPage.bind(this, feedback, data, siteId, timemodified, result),
 | 
				
			||||||
 | 
					            blocking: true,
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Execute all the processes in order to solve dependencies.
 | 
				
			||||||
 | 
					        await CoreUtils.executeOrderedPromises(orderedData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (result.updated) {
 | 
				
			||||||
 | 
					            // Data has been sent to server, update data.
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const module = await CoreCourse.getModuleBasicInfoByInstance(feedbackId, 'feedback', siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                await this.prefetchAfterUpdate(AddonModFeedbackPrefetchHandler.instance, module, courseId, undefined, siteId);
 | 
				
			||||||
 | 
					            } catch {
 | 
				
			||||||
 | 
					                // Ignore errors.
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Sync finished, set sync time.
 | 
				
			||||||
 | 
					        await CoreUtils.ignoreErrors(this.setSyncTime(feedbackId, siteId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return result;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Convenience function to sync process page calls.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedback Feedback object.
 | 
				
			||||||
 | 
					     * @param data Response data.
 | 
				
			||||||
 | 
					     * @param siteId Site Id.
 | 
				
			||||||
 | 
					     * @param timemodified Current completed modification time.
 | 
				
			||||||
 | 
					     * @param result Result object to be modified.
 | 
				
			||||||
 | 
					     * @return Resolve when done or rejected with error.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async processPage(
 | 
				
			||||||
 | 
					        feedback: AddonModFeedbackWSFeedback,
 | 
				
			||||||
 | 
					        data: AddonModFeedbackOfflineResponse,
 | 
				
			||||||
 | 
					        siteId: string,
 | 
				
			||||||
 | 
					        timemodified: number,
 | 
				
			||||||
 | 
					        result: AddonModFeedbackSyncResult,
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        // Delete all pages that are submitted before changing website.
 | 
				
			||||||
 | 
					        if (timemodified > data.timemodified) {
 | 
				
			||||||
 | 
					            return AddonModFeedbackOffline.deleteFeedbackPageResponses(feedback.id, data.page, siteId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            await AddonModFeedback.processPageOnline(feedback.id, data.page, data.responses, false, siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result.updated = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await AddonModFeedbackOffline.deleteFeedbackPageResponses(feedback.id, data.page, siteId);
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            if (!CoreUtils.isWebServiceError(error)) {
 | 
				
			||||||
 | 
					                // Couldn't connect to server, reject.
 | 
				
			||||||
 | 
					                throw error;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // The WebService has thrown an error, this means that responses cannot be submitted. Delete them.
 | 
				
			||||||
 | 
					            result.updated = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await AddonModFeedbackOffline.deleteFeedbackPageResponses(feedback.id, data.page, siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Responses deleted, add a warning.
 | 
				
			||||||
 | 
					            this.addOfflineDataDeletedWarning(
 | 
				
			||||||
 | 
					                result.warnings,
 | 
				
			||||||
 | 
					                feedback.name,
 | 
				
			||||||
 | 
					                error,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackSync = makeSingleton(AddonModFeedbackSyncProvider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Data returned by a feedback sync.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackSyncResult = {
 | 
				
			||||||
 | 
					    warnings: string[]; // List of warnings.
 | 
				
			||||||
 | 
					    updated: boolean; // Whether some data was sent to the server or offline data was updated.
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Data passed to AUTO_SYNCED event.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export type AddonModFeedbackAutoSyncData = {
 | 
				
			||||||
 | 
					    feedbackId: number;
 | 
				
			||||||
 | 
					    warnings: string[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										1762
									
								
								src/addons/mod/feedback/services/feedback.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1762
									
								
								src/addons/mod/feedback/services/feedback.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										92
									
								
								src/addons/mod/feedback/services/handlers/analysis-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/addons/mod/feedback/services/handlers/analysis-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					// (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 { 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 { AddonModFeedback } from '../feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackModuleHandlerService } from './module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Content links handler for a feedback analysis.
 | 
				
			||||||
 | 
					 * Match mod/feedback/analysis.php with a valid feedback id.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackAnalysisLinkHandlerService extends CoreContentLinksHandlerBase {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'AddonModFeedbackAnalysisLinkHandler';
 | 
				
			||||||
 | 
					    featureName = 'CoreCourseModuleDelegate_AddonModFeedback';
 | 
				
			||||||
 | 
					    pattern = /\/mod\/feedback\/analysis\.php.*([&?]id=\d+)/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
 | 
				
			||||||
 | 
					        return [{
 | 
				
			||||||
 | 
					            action: async (siteId: string) => {
 | 
				
			||||||
 | 
					                const modal = await CoreDomUtils.showModalLoading();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const moduleId = Number(params.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    const moduleBasicInfo = await CoreCourse.getModuleBasicInfo(moduleId, siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Get the module.
 | 
				
			||||||
 | 
					                    const module = await CoreCourse.getModule(
 | 
				
			||||||
 | 
					                        moduleId,
 | 
				
			||||||
 | 
					                        moduleBasicInfo.course,
 | 
				
			||||||
 | 
					                        moduleBasicInfo.section,
 | 
				
			||||||
 | 
					                        false,
 | 
				
			||||||
 | 
					                        false,
 | 
				
			||||||
 | 
					                        siteId,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    CoreNavigator.navigateToSitePath(
 | 
				
			||||||
 | 
					                        AddonModFeedbackModuleHandlerService.PAGE_NAME + `/${module.course}/${module.id}`,
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            params: {
 | 
				
			||||||
 | 
					                                module,
 | 
				
			||||||
 | 
					                                tab: 'analysis',
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            siteId,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                } catch (error) {
 | 
				
			||||||
 | 
					                    CoreDomUtils.showErrorModalDefault(error, 'Error opening link.');
 | 
				
			||||||
 | 
					                } finally {
 | 
				
			||||||
 | 
					                    modal.dismiss();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async isEnabled(siteId: string, url: string, params: Record<string, string>): Promise<boolean> {
 | 
				
			||||||
 | 
					        if (typeof params.id == 'undefined') {
 | 
				
			||||||
 | 
					            // Cannot treat the URL.
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return AddonModFeedback.isPluginEnabled(siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackAnalysisLinkHandler = makeSingleton(AddonModFeedbackAnalysisLinkHandlerService);
 | 
				
			||||||
							
								
								
									
										80
									
								
								src/addons/mod/feedback/services/handlers/complete-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/addons/mod/feedback/services/handlers/complete-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					// (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 { 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 { AddonModFeedback } from '../feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackModuleHandlerService } from './module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Content links handler for feedback complete questions.
 | 
				
			||||||
 | 
					 * Match mod/feedback/complete.php with a valid feedback id.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackCompleteLinkHandlerService extends CoreContentLinksHandlerBase {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'AddonModFeedbackCompleteLinkHandler';
 | 
				
			||||||
 | 
					    featureName = 'CoreCourseModuleDelegate_AddonModFeedback';
 | 
				
			||||||
 | 
					    pattern = /\/mod\/feedback\/complete\.php.*([?&](id|gopage)=\d+)/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
 | 
				
			||||||
 | 
					        return [{
 | 
				
			||||||
 | 
					            action: async (siteId: string) => {
 | 
				
			||||||
 | 
					                const modal = await CoreDomUtils.showModalLoading();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const moduleId = Number(params.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    const module = await CoreCourse.getModuleBasicInfo(moduleId, siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    CoreNavigator.navigateToSitePath(
 | 
				
			||||||
 | 
					                        AddonModFeedbackModuleHandlerService.PAGE_NAME + `/${module.course}/${module.id}/form`,
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            params: {
 | 
				
			||||||
 | 
					                                page: typeof params.gopage != 'undefined' ? Number(params.gopage) : undefined,
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            siteId,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                } catch (error) {
 | 
				
			||||||
 | 
					                    CoreDomUtils.showErrorModalDefault(error, 'Error opening link.');
 | 
				
			||||||
 | 
					                } finally {
 | 
				
			||||||
 | 
					                    modal.dismiss();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async isEnabled(siteId: string, url: string, params: Record<string, string>): Promise<boolean> {
 | 
				
			||||||
 | 
					        if (typeof params.id == 'undefined') {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return AddonModFeedback.isPluginEnabled(siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackCompleteLinkHandler = makeSingleton(AddonModFeedbackCompleteLinkHandlerService);
 | 
				
			||||||
							
								
								
									
										47
									
								
								src/addons/mod/feedback/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/addons/mod/feedback/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					// (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 { AddonModFeedback } from '../feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Handler to treat links to feedback.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'AddonModFeedbackLinkHandler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super('AddonModFeedback', 'feedback');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check if the handler is enabled for a certain site (site + user) and a URL.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param siteId The site ID.
 | 
				
			||||||
 | 
					     * @param url The URL to treat.
 | 
				
			||||||
 | 
					     * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
 | 
				
			||||||
 | 
					     * @param courseId Course ID related to the URL. Optional but recommended.
 | 
				
			||||||
 | 
					     * @return Whether the handler is enabled for the URL and site.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isEnabled(): Promise<boolean> {
 | 
				
			||||||
 | 
					        return AddonModFeedback.isPluginEnabled();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackIndexLinkHandler = makeSingleton(AddonModFeedbackIndexLinkHandlerService);
 | 
				
			||||||
							
								
								
									
										41
									
								
								src/addons/mod/feedback/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/addons/mod/feedback/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					// (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 { AddonModFeedback } from '../feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Handler to treat links to feedback list page.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackListLinkHandlerService extends CoreContentLinksModuleListHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'AddonModFeedbackListLinkHandler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor() {
 | 
				
			||||||
 | 
					        super('AddonModFeedback', 'feedback');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isEnabled(): Promise<boolean> {
 | 
				
			||||||
 | 
					        return AddonModFeedback.isPluginEnabled();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackListLinkHandler = makeSingleton(AddonModFeedbackListLinkHandlerService);
 | 
				
			||||||
							
								
								
									
										84
									
								
								src/addons/mod/feedback/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/addons/mod/feedback/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					// (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 { AddonModFeedback } from '../feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackIndexComponent } from '../../components/index';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Handler to support feedback modules.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackModuleHandlerService implements CoreCourseModuleHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static readonly PAGE_NAME = 'mod_feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'AddonModFeedback';
 | 
				
			||||||
 | 
					    modName = 'feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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]: false,
 | 
				
			||||||
 | 
					        [CoreConstants.FEATURE_GRADE_OUTCOMES]: false,
 | 
				
			||||||
 | 
					        [CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
 | 
				
			||||||
 | 
					        [CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isEnabled(): Promise<boolean> {
 | 
				
			||||||
 | 
					        return AddonModFeedback.isPluginEnabled();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getData(module: CoreCourseAnyModuleData): CoreCourseModuleHandlerData {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined),
 | 
				
			||||||
 | 
					            title: module.name,
 | 
				
			||||||
 | 
					            class: 'addon-mod_feedback-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(AddonModFeedbackModuleHandlerService.PAGE_NAME + routeParams, options);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async getMainComponent(): Promise<Type<unknown>> {
 | 
				
			||||||
 | 
					        return AddonModFeedbackIndexComponent;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackModuleHandler = makeSingleton(AddonModFeedbackModuleHandlerService);
 | 
				
			||||||
							
								
								
									
										228
									
								
								src/addons/mod/feedback/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								src/addons/mod/feedback/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,228 @@
 | 
				
			|||||||
 | 
					// (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 { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
 | 
				
			||||||
 | 
					import { CoreCourseAnyModuleData, CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
				
			||||||
 | 
					import { CoreFilepool } from '@services/filepool';
 | 
				
			||||||
 | 
					import { CoreGroups } from '@services/groups';
 | 
				
			||||||
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
 | 
					import { CoreTimeUtils } from '@services/utils/time';
 | 
				
			||||||
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    AddonModFeedback,
 | 
				
			||||||
 | 
					    AddonModFeedbackGetFeedbackAccessInformationWSResponse,
 | 
				
			||||||
 | 
					    AddonModFeedbackProvider,
 | 
				
			||||||
 | 
					    AddonModFeedbackWSFeedback,
 | 
				
			||||||
 | 
					} from '../feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackSync, AddonModFeedbackSyncResult } from '../feedback-sync';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Handler to prefetch feedbacks.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'AddonModFeedback';
 | 
				
			||||||
 | 
					    modName = 'feedback';
 | 
				
			||||||
 | 
					    component = AddonModFeedbackProvider.COMPONENT;
 | 
				
			||||||
 | 
					    updatesNames = /^configuration$|^.*files$|^attemptsfinished|^attemptsunfinished$/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
 | 
					        let files: CoreWSFile[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const feedback = await AddonModFeedback.getFeedback(courseId, module.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Get intro files and page after submit files.
 | 
				
			||||||
 | 
					        files = feedback.pageaftersubmitfiles || [];
 | 
				
			||||||
 | 
					        files = files.concat(this.getIntroFilesFromInstance(module, feedback));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const response = await AddonModFeedback.getItems(feedback.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            response.items.forEach((item) => {
 | 
				
			||||||
 | 
					                files = files.concat(item.itemfiles);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            // Ignore errors.
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return files;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
 | 
					        const feedback = await CoreUtils.ignoreErrors(AddonModFeedback.getFeedback(courseId, module.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.getIntroFilesFromInstance(module, feedback);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateContent(moduleId: number, courseId: number): Promise<void> {
 | 
				
			||||||
 | 
					        return AddonModFeedback.invalidateContent(moduleId, courseId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateModule(module: CoreCourseAnyModuleData, courseId: number): Promise<void> {
 | 
				
			||||||
 | 
					        return AddonModFeedback.invalidateFeedbackData(courseId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async isDownloadable(module: CoreCourseAnyModuleData, courseId: number): Promise<boolean> {
 | 
				
			||||||
 | 
					        const feedback = await AddonModFeedback.getFeedback(courseId, module.id, {
 | 
				
			||||||
 | 
					            readingStrategy: CoreSitesReadingStrategy.PreferCache,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const now = CoreTimeUtils.timestamp();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check time first if available.
 | 
				
			||||||
 | 
					        if (feedback.timeopen && feedback.timeopen > now) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (feedback.timeclose && feedback.timeclose < now) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const accessData = await AddonModFeedback.getFeedbackAccessInformation(feedback.id, { cmId: module.id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return accessData.isopen;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isEnabled(): Promise<boolean> {
 | 
				
			||||||
 | 
					        return AddonModFeedback.isPluginEnabled();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    prefetch(module: CoreCourseAnyModuleData, courseId?: number): Promise<void> {
 | 
				
			||||||
 | 
					        return this.prefetchPackage(module, courseId, this.prefetchFeedback.bind(this, module, courseId));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Prefetch a feedback.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param module Module.
 | 
				
			||||||
 | 
					     * @param courseId Course ID the module belongs to.
 | 
				
			||||||
 | 
					     * @return Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async prefetchFeedback(module: CoreCourseAnyModuleData, courseId: number): Promise<void> {
 | 
				
			||||||
 | 
					        const siteId = CoreSites.getCurrentSiteId();
 | 
				
			||||||
 | 
					        const commonOptions = {
 | 
				
			||||||
 | 
					            readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
 | 
				
			||||||
 | 
					            siteId,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        const modOptions = {
 | 
				
			||||||
 | 
					            cmId: module.id,
 | 
				
			||||||
 | 
					            ...commonOptions, // Include all common options.
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Prefetch the feedback data.
 | 
				
			||||||
 | 
					        const feedback = await AddonModFeedback.getFeedback(courseId, module.id, commonOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let files: CoreWSFile[] = feedback.pageaftersubmitfiles || [];
 | 
				
			||||||
 | 
					        files = files.concat(this.getIntroFilesFromInstance(module, feedback));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const accessData = await AddonModFeedback.getFeedbackAccessInformation(feedback.id, modOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const promises: Promise<unknown>[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (accessData.canedititems || accessData.canviewreports) {
 | 
				
			||||||
 | 
					            // Get all groups analysis.
 | 
				
			||||||
 | 
					            promises.push(AddonModFeedback.getAnalysis(feedback.id, modOptions));
 | 
				
			||||||
 | 
					            promises.push(this.prefetchAllGroupsAnalysis(feedback, accessData, modOptions));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        promises.push(AddonModFeedback.getItems(feedback.id, commonOptions).then((response) => {
 | 
				
			||||||
 | 
					            response.items.forEach((item) => {
 | 
				
			||||||
 | 
					                files = files.concat(item.itemfiles);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return CoreFilepool.addFilesToQueue(siteId, files, this.component, module.id);
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (accessData.cancomplete && accessData.cansubmit && !accessData.isempty) {
 | 
				
			||||||
 | 
					            // Send empty data, so it will recover last completed feedback attempt values.
 | 
				
			||||||
 | 
					            promises.push(AddonModFeedback.processPageOnline(feedback.id, 0, {}, false, siteId).then(() => Promise.all([
 | 
				
			||||||
 | 
					                AddonModFeedback.getCurrentValues(feedback.id, modOptions),
 | 
				
			||||||
 | 
					                AddonModFeedback.getResumePage(feedback.id, modOptions),
 | 
				
			||||||
 | 
					            ])));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await Promise.all(promises);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Prefetch all groups analysis.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param feedback Feedback.
 | 
				
			||||||
 | 
					     * @param accessData Access info.
 | 
				
			||||||
 | 
					     * @param modOptions Options.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected async prefetchAllGroupsAnalysis(
 | 
				
			||||||
 | 
					        feedback: AddonModFeedbackWSFeedback,
 | 
				
			||||||
 | 
					        accessData: AddonModFeedbackGetFeedbackAccessInformationWSResponse,
 | 
				
			||||||
 | 
					        modOptions: CoreCourseCommonModWSOptions,
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        const groupInfo = await CoreGroups.getActivityGroupInfo(feedback.coursemodule, true, undefined, modOptions.siteId, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const promises: Promise<unknown>[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!groupInfo.groups || groupInfo.groups.length == 0) {
 | 
				
			||||||
 | 
					            groupInfo.groups = [{ id: 0, name: '' }];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        groupInfo.groups.forEach((group) => {
 | 
				
			||||||
 | 
					            const groupOptions = {
 | 
				
			||||||
 | 
					                groupId: group.id,
 | 
				
			||||||
 | 
					                ...modOptions, // Include all mod options.
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            promises.push(AddonModFeedback.getAnalysis(feedback.id, groupOptions));
 | 
				
			||||||
 | 
					            promises.push(AddonModFeedback.getAllResponsesAnalysis(feedback.id, groupOptions));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!accessData.isanonymous) {
 | 
				
			||||||
 | 
					                promises.push(AddonModFeedback.getAllNonRespondents(feedback.id, groupOptions));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await Promise.all(promises);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    sync(module: CoreCourseAnyModuleData, courseId: number, siteId?: string): Promise<AddonModFeedbackSyncResult> {
 | 
				
			||||||
 | 
					        return AddonModFeedbackSync.syncFeedback(module.instance!, siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackPrefetchHandler = makeSingleton(AddonModFeedbackPrefetchHandlerService);
 | 
				
			||||||
							
								
								
									
										80
									
								
								src/addons/mod/feedback/services/handlers/print-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/addons/mod/feedback/services/handlers/print-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					// (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 { 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 { AddonModFeedback } from '../feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackModuleHandlerService } from './module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Content links handler for feedback print questions.
 | 
				
			||||||
 | 
					 * Match mod/feedback/print.php with a valid feedback id.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackPrintLinkHandlerService extends CoreContentLinksHandlerBase {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'AddonModFeedbackPrintLinkHandler';
 | 
				
			||||||
 | 
					    featureName = 'CoreCourseModuleDelegate_AddonModFeedback';
 | 
				
			||||||
 | 
					    pattern = /\/mod\/feedback\/print\.php.*([?&](id)=\d+)/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
 | 
				
			||||||
 | 
					        return [{
 | 
				
			||||||
 | 
					            action: async (siteId: string) => {
 | 
				
			||||||
 | 
					                const modal = await CoreDomUtils.showModalLoading();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const moduleId = Number(params.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    const module = await CoreCourse.getModuleBasicInfo(moduleId, siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    CoreNavigator.navigateToSitePath(
 | 
				
			||||||
 | 
					                        AddonModFeedbackModuleHandlerService.PAGE_NAME + `/${module.course}/${module.id}/form`,
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            params: {
 | 
				
			||||||
 | 
					                                preview: true,
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            siteId,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                } catch (error) {
 | 
				
			||||||
 | 
					                    CoreDomUtils.showErrorModalDefault(error, 'Error opening link.');
 | 
				
			||||||
 | 
					                } finally {
 | 
				
			||||||
 | 
					                    modal.dismiss();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async isEnabled(siteId: string, url: string, params: Record<string, string>): Promise<boolean> {
 | 
				
			||||||
 | 
					        if (typeof params.id == 'undefined') {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return AddonModFeedback.isPluginEnabled(siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackPrintLinkHandler = makeSingleton(AddonModFeedbackPrintLinkHandlerService);
 | 
				
			||||||
							
								
								
									
										70
									
								
								src/addons/mod/feedback/services/handlers/push-click.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/addons/mod/feedback/services/handlers/push-click.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					// (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 { CoreCourseHelper } from '@features/course/services/course-helper';
 | 
				
			||||||
 | 
					import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate';
 | 
				
			||||||
 | 
					import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications';
 | 
				
			||||||
 | 
					import { CoreUrlUtils } from '@services/utils/url';
 | 
				
			||||||
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
 | 
					import { AddonModFeedback } from '../feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackHelper } from '../feedback-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Handler for feedback push notifications clicks.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackPushClickHandlerService implements CorePushNotificationsClickHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'AddonModFeedbackPushClickHandler';
 | 
				
			||||||
 | 
					    priority = 200;
 | 
				
			||||||
 | 
					    featureName = 'CoreCourseModuleDelegate_AddonModFeedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async handles(notification: CorePushNotificationsNotificationBasicData): Promise<boolean> {
 | 
				
			||||||
 | 
					        if (CoreUtils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'mod_feedback' &&
 | 
				
			||||||
 | 
					                (notification.name == 'submission' || notification.name == 'message')) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return AddonModFeedback.isPluginEnabled(notification.site);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    handleClick(notification: AddonModFeedbackPushNotificationData): Promise<void> {
 | 
				
			||||||
 | 
					        const contextUrlParams = CoreUrlUtils.extractUrlParams(notification.contexturl!);
 | 
				
			||||||
 | 
					        const courseId = Number(notification.courseid);
 | 
				
			||||||
 | 
					        const moduleId = Number(contextUrlParams.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (notification.name == 'submission') {
 | 
				
			||||||
 | 
					            return AddonModFeedbackHelper.handleShowEntriesLink(contextUrlParams, notification.site);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return CoreCourseHelper.navigateToModule(moduleId, notification.site, courseId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackPushClickHandler = makeSingleton(AddonModFeedbackPushClickHandlerService);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type AddonModFeedbackPushNotificationData = CorePushNotificationsNotificationBasicData & {
 | 
				
			||||||
 | 
					    contexturl?: string;
 | 
				
			||||||
 | 
					    courseid?: number | string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					// (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 { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
 | 
				
			||||||
 | 
					import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
 | 
				
			||||||
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
 | 
					import { AddonModFeedback } from '../feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackHelper } from '../feedback-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Content links handler for feedback show entries questions.
 | 
				
			||||||
 | 
					 * Match mod/feedback/show_entries.php with a valid feedback id.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackShowEntriesLinkHandlerService extends CoreContentLinksHandlerBase {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'AddonModFeedbackShowEntriesLinkHandler';
 | 
				
			||||||
 | 
					    featureName = 'CoreCourseModuleDelegate_AddonModFeedback';
 | 
				
			||||||
 | 
					    pattern = /\/mod\/feedback\/show_entries\.php.*([?&](id|showcompleted)=\d+)/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
 | 
				
			||||||
 | 
					        return [{
 | 
				
			||||||
 | 
					            action: (siteId: string) => {
 | 
				
			||||||
 | 
					                AddonModFeedbackHelper.handleShowEntriesLink(params, siteId);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async isEnabled(siteId: string, url: string, params: Record<string, string>): Promise<boolean> {
 | 
				
			||||||
 | 
					        if (typeof params.id == 'undefined') {
 | 
				
			||||||
 | 
					            // Cannot treat the URL.
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return AddonModFeedback.isPluginEnabled(siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackShowEntriesLinkHandler = makeSingleton(AddonModFeedbackShowEntriesLinkHandlerService);
 | 
				
			||||||
@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					// (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 { 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 { AddonModFeedback } from '../feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackModuleHandlerService } from './module';
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Content links handler for feedback show non respondents.
 | 
				
			||||||
 | 
					 * Match mod/feedback/show_nonrespondents.php with a valid feedback id.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackShowNonRespondentsLinkHandlerService extends CoreContentLinksHandlerBase {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'AddonModFeedbackShowNonRespondentsLinkHandler';
 | 
				
			||||||
 | 
					    featureName = 'CoreCourseModuleDelegate_AddonModFeedback';
 | 
				
			||||||
 | 
					    pattern = /\/mod\/feedback\/show_nonrespondents\.php.*([?&](id)=\d+)/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
 | 
				
			||||||
 | 
					        return [{
 | 
				
			||||||
 | 
					            action: async (siteId: string) => {
 | 
				
			||||||
 | 
					                const modal = await CoreDomUtils.showModalLoading();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const moduleId = Number(params.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try {
 | 
				
			||||||
 | 
					                    const module = await CoreCourse.getModuleBasicInfo(moduleId, siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    await CoreNavigator.navigateToSitePath(
 | 
				
			||||||
 | 
					                        AddonModFeedbackModuleHandlerService.PAGE_NAME + `/${module.course}/${module.id}/nonrespondents`,
 | 
				
			||||||
 | 
					                        { siteId },
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                } catch (error) {
 | 
				
			||||||
 | 
					                    CoreDomUtils.showErrorModalDefault(error, 'Error opening link.');
 | 
				
			||||||
 | 
					                } finally {
 | 
				
			||||||
 | 
					                    modal.dismiss();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @inheritdoc
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async isEnabled(siteId: string, url: string, params: Record<string, string>): Promise<boolean> {
 | 
				
			||||||
 | 
					        if (typeof params.id == 'undefined') {
 | 
				
			||||||
 | 
					            // Cannot treat the URL.
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return AddonModFeedback.isPluginEnabled(siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackShowNonRespondentsLinkHandler = makeSingleton(AddonModFeedbackShowNonRespondentsLinkHandlerService);
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/addons/mod/feedback/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/addons/mod/feedback/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					// (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 { AddonModFeedbackSync } from '../feedback-sync';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Synchronization cron handler.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable({ providedIn: 'root' })
 | 
				
			||||||
 | 
					export class AddonModFeedbackSyncCronHandlerService implements CoreCronHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    name = 'AddonModFeedbackSyncCronHandler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Execute the process.
 | 
				
			||||||
 | 
					     * Receives the ID of the site affected, undefined for all sites.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param siteId ID of the site affected, undefined for all sites.
 | 
				
			||||||
 | 
					     * @param force Wether the execution is forced (manual sync).
 | 
				
			||||||
 | 
					     * @return Promise resolved when done, rejected if failure.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    execute(siteId?: string, force?: boolean): Promise<void> {
 | 
				
			||||||
 | 
					        return AddonModFeedbackSync.syncAllFeedbacks(siteId, force);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the time between consecutive executions.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return Time between consecutive executions (in ms).
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getInterval(): number {
 | 
				
			||||||
 | 
					        return AddonModFeedbackSync.syncInterval;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const AddonModFeedbackSyncCronHandler = makeSingleton(AddonModFeedbackSyncCronHandlerService);
 | 
				
			||||||
@ -14,13 +14,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { Component, ViewChild, ElementRef, Input, OnInit } from '@angular/core';
 | 
					import { Component, ViewChild, ElementRef, Input, OnInit } from '@angular/core';
 | 
				
			||||||
import { FormControl } from '@angular/forms';
 | 
					import { FormControl } from '@angular/forms';
 | 
				
			||||||
import { CoreFileEntry, CoreFileUploader } from '@features/fileuploader/services/fileuploader';
 | 
					import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
 | 
				
			||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
import { ModalController, Translate } from '@singletons';
 | 
					import { ModalController, Translate } from '@singletons';
 | 
				
			||||||
import { AddonModForumData, AddonModForumPost, AddonModForumReply } from '@addons/mod/forum/services/forum';
 | 
					import { AddonModForumData, AddonModForumPost, AddonModForumReply } from '@addons/mod/forum/services/forum';
 | 
				
			||||||
import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper';
 | 
					import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper';
 | 
				
			||||||
import { CoreForms } from '@singletons/form';
 | 
					import { CoreForms } from '@singletons/form';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Page that displays a form to edit discussion post.
 | 
					 * Page that displays a form to edit discussion post.
 | 
				
			||||||
 | 
				
			|||||||
@ -38,11 +38,10 @@ import {
 | 
				
			|||||||
    AddonModForumProvider,
 | 
					    AddonModForumProvider,
 | 
				
			||||||
    AddonModForumReply,
 | 
					    AddonModForumReply,
 | 
				
			||||||
    AddonModForumUpdateDiscussionPostWSOptionsObject,
 | 
					    AddonModForumUpdateDiscussionPostWSOptionsObject,
 | 
				
			||||||
    AddonModForumWSPostAttachment,
 | 
					 | 
				
			||||||
} from '../../services/forum';
 | 
					} from '../../services/forum';
 | 
				
			||||||
import { CoreTag } from '@features/tag/services/tag';
 | 
					import { CoreTag } from '@features/tag/services/tag';
 | 
				
			||||||
import { ModalController, PopoverController, Translate } from '@singletons';
 | 
					import { ModalController, PopoverController, Translate } from '@singletons';
 | 
				
			||||||
import { CoreFileEntry, CoreFileUploader } from '@features/fileuploader/services/fileuploader';
 | 
					import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
 | 
				
			||||||
import { IonContent } from '@ionic/angular';
 | 
					import { IonContent } from '@ionic/angular';
 | 
				
			||||||
import { AddonModForumSync } from '../../services/forum-sync';
 | 
					import { AddonModForumSync } from '../../services/forum-sync';
 | 
				
			||||||
import { CoreSync } from '@services/sync';
 | 
					import { CoreSync } from '@services/sync';
 | 
				
			||||||
@ -54,6 +53,7 @@ import { AddonModForumPostOptionsMenuComponent } from '../post-options-menu/post
 | 
				
			|||||||
import { AddonModForumEditPostComponent } from '../edit-post/edit-post';
 | 
					import { AddonModForumEditPostComponent } from '../edit-post/edit-post';
 | 
				
			||||||
import { CoreRatingInfo } from '@features/rating/services/rating';
 | 
					import { CoreRatingInfo } from '@features/rating/services/rating';
 | 
				
			||||||
import { CoreForms } from '@singletons/form';
 | 
					import { CoreForms } from '@singletons/form';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.).
 | 
					 * Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.).
 | 
				
			||||||
@ -186,7 +186,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
 | 
				
			|||||||
        isEditing?: boolean,
 | 
					        isEditing?: boolean,
 | 
				
			||||||
        subject?: string,
 | 
					        subject?: string,
 | 
				
			||||||
        message?: string,
 | 
					        message?: string,
 | 
				
			||||||
        files?: (CoreFileEntry | AddonModForumWSPostAttachment)[],
 | 
					        files?: CoreFileEntry[],
 | 
				
			||||||
        isPrivate?: boolean,
 | 
					        isPrivate?: boolean,
 | 
				
			||||||
    ): void {
 | 
					    ): void {
 | 
				
			||||||
        // Delete the local files from the tmp folder if any.
 | 
					        // Delete the local files from the tmp folder if any.
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { Injectable } from '@angular/core';
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file/ngx';
 | 
					import { FileEntry } from '@ionic-native/file/ngx';
 | 
				
			||||||
import { CoreFileEntry, CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
					import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
				
			||||||
import { CoreUser } from '@features/user/services/user';
 | 
					import { CoreUser } from '@features/user/services/user';
 | 
				
			||||||
import { CoreApp } from '@services/app';
 | 
					import { CoreApp } from '@services/app';
 | 
				
			||||||
import { CoreFile } from '@services/file';
 | 
					import { CoreFile } from '@services/file';
 | 
				
			||||||
@ -31,6 +31,7 @@ import {
 | 
				
			|||||||
    AddonModForumProvider,
 | 
					    AddonModForumProvider,
 | 
				
			||||||
} from './forum';
 | 
					} from './forum';
 | 
				
			||||||
import { AddonModForumDiscussionOptions, AddonModForumOffline, AddonModForumOfflineReply } from './forum-offline';
 | 
					import { AddonModForumDiscussionOptions, AddonModForumOffline, AddonModForumOfflineReply } from './forum-offline';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Service that provides some features for forums.
 | 
					 * Service that provides some features for forums.
 | 
				
			||||||
 | 
				
			|||||||
@ -16,17 +16,17 @@ import { Injectable } from '@angular/core';
 | 
				
			|||||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
					import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
				
			||||||
import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
					import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
				
			||||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
					import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
				
			||||||
import { CoreFileEntry } from '@features/fileuploader/services/fileuploader';
 | 
					 | 
				
			||||||
import { CoreRatingInfo } from '@features/rating/services/rating';
 | 
					import { CoreRatingInfo } from '@features/rating/services/rating';
 | 
				
			||||||
import { CoreTagItem } from '@features/tag/services/tag';
 | 
					import { CoreTagItem } from '@features/tag/services/tag';
 | 
				
			||||||
import { CoreUser } from '@features/user/services/user';
 | 
					import { CoreUser } from '@features/user/services/user';
 | 
				
			||||||
import { CoreApp } from '@services/app';
 | 
					import { CoreApp } from '@services/app';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
import { CoreFilepool } from '@services/filepool';
 | 
					import { CoreFilepool } from '@services/filepool';
 | 
				
			||||||
import { CoreGroups } from '@services/groups';
 | 
					import { CoreGroups } from '@services/groups';
 | 
				
			||||||
import { CoreSitesCommonWSOptions, CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSitesCommonWSOptions, CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreUrlUtils } from '@services/utils/url';
 | 
					import { CoreUrlUtils } from '@services/utils/url';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
 | 
					import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning, CoreWSStoredFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton, Translate } from '@singletons';
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumReplyOptions } from './forum-offline';
 | 
					import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumReplyOptions } from './forum-offline';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1469,7 +1469,7 @@ export type AddonModForumPost = {
 | 
				
			|||||||
        canreplyprivately?: boolean; // Whether the user can post a private reply.
 | 
					        canreplyprivately?: boolean; // Whether the user can post a private reply.
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    attachment?: 0 | 1;
 | 
					    attachment?: 0 | 1;
 | 
				
			||||||
    attachments?: (CoreFileEntry | AddonModForumWSPostAttachment)[];
 | 
					    attachments?: CoreFileEntry[];
 | 
				
			||||||
    messageinlinefiles?: CoreWSExternalFile[];
 | 
					    messageinlinefiles?: CoreWSExternalFile[];
 | 
				
			||||||
    haswordcount?: boolean; // Haswordcount.
 | 
					    haswordcount?: boolean; // Haswordcount.
 | 
				
			||||||
    wordcount?: number; // Wordcount.
 | 
					    wordcount?: number; // Wordcount.
 | 
				
			||||||
@ -1580,7 +1580,7 @@ export type AddonModForumReply = {
 | 
				
			|||||||
    id: number;
 | 
					    id: number;
 | 
				
			||||||
    subject: string;
 | 
					    subject: string;
 | 
				
			||||||
    message: string;
 | 
					    message: string;
 | 
				
			||||||
    files: (CoreFileEntry | AddonModForumWSPostAttachment)[];
 | 
					    files: CoreFileEntry[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@ -1600,37 +1600,6 @@ export type AddonModForumSortOrder = {
 | 
				
			|||||||
    value: number;
 | 
					    value: number;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Forum post attachement data returned by web services.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export type AddonModForumWSPostAttachment = {
 | 
					 | 
				
			||||||
    contextid: number; // Contextid.
 | 
					 | 
				
			||||||
    component: string; // Component.
 | 
					 | 
				
			||||||
    filearea: string; // Filearea.
 | 
					 | 
				
			||||||
    itemid: number; // Itemid.
 | 
					 | 
				
			||||||
    filepath: string; // Filepath.
 | 
					 | 
				
			||||||
    filename: string; // Filename.
 | 
					 | 
				
			||||||
    isdir: boolean; // Isdir.
 | 
					 | 
				
			||||||
    isimage: boolean; // Isimage.
 | 
					 | 
				
			||||||
    timemodified: number; // Timemodified.
 | 
					 | 
				
			||||||
    timecreated: number; // Timecreated.
 | 
					 | 
				
			||||||
    filesize: number; // Filesize.
 | 
					 | 
				
			||||||
    author: string; // Author.
 | 
					 | 
				
			||||||
    license: string; // License.
 | 
					 | 
				
			||||||
    filenameshort: string; // Filenameshort.
 | 
					 | 
				
			||||||
    filesizeformatted: string; // Filesizeformatted.
 | 
					 | 
				
			||||||
    icon: string; // Icon.
 | 
					 | 
				
			||||||
    timecreatedformatted: string; // Timecreatedformatted.
 | 
					 | 
				
			||||||
    timemodifiedformatted: string; // Timemodifiedformatted.
 | 
					 | 
				
			||||||
    url: string; // Url.
 | 
					 | 
				
			||||||
    urls: {
 | 
					 | 
				
			||||||
        export?: string; // The URL used to export the attachment.
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    html: {
 | 
					 | 
				
			||||||
        plagiarism?: string; // The HTML source for the Plagiarism Response.
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Forum post data returned by web services.
 | 
					 * Forum post data returned by web services.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@ -1693,7 +1662,7 @@ export type AddonModForumWSPost = {
 | 
				
			|||||||
        markasunread?: string; // The URL used to mark the post as unread.
 | 
					        markasunread?: string; // The URL used to mark the post as unread.
 | 
				
			||||||
        discuss?: string; // Discuss.
 | 
					        discuss?: string; // Discuss.
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    attachments: AddonModForumWSPostAttachment[]; // Attachments.
 | 
					    attachments: CoreWSStoredFile[]; // Attachments.
 | 
				
			||||||
    tags?: { // Tags.
 | 
					    tags?: { // Tags.
 | 
				
			||||||
        id: number; // The ID of the Tag.
 | 
					        id: number; // The ID of the Tag.
 | 
				
			||||||
        tagid: number; // The tagid.
 | 
					        tagid: number; // The tagid.
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/
 | 
				
			|||||||
import { AddonModForum, AddonModForumData, AddonModForumPost, AddonModForumProvider } from '../forum';
 | 
					import { AddonModForum, AddonModForumData, AddonModForumPost, AddonModForumProvider } from '../forum';
 | 
				
			||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreFilepool } from '@services/filepool';
 | 
					import { CoreFilepool } from '@services/filepool';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { CoreCourse, CoreCourseAnyModuleData, CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
					import { CoreCourse, CoreCourseAnyModuleData, CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
				
			||||||
import { CoreUser } from '@features/user/services/user';
 | 
					import { CoreUser } from '@features/user/services/user';
 | 
				
			||||||
import { CoreGroups, CoreGroupsProvider } from '@services/groups';
 | 
					import { CoreGroups, CoreGroupsProvider } from '@services/groups';
 | 
				
			||||||
@ -44,7 +44,7 @@ export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefe
 | 
				
			|||||||
     * @param single True if we're downloading a single module, false if we're downloading a whole section.
 | 
					     * @param single True if we're downloading a single module, false if we're downloading a whole section.
 | 
				
			||||||
     * @return Promise resolved with the list of files.
 | 
					     * @return Promise resolved with the list of files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
					    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const forum = await AddonModForum.getForum(courseId, module.id);
 | 
					            const forum = await AddonModForum.getForum(courseId, module.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -69,13 +69,13 @@ export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefe
 | 
				
			|||||||
     * @param posts Forum posts.
 | 
					     * @param posts Forum posts.
 | 
				
			||||||
     * @return Files.
 | 
					     * @return Files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected getPostsFiles(posts: AddonModForumPost[]): CoreWSExternalFile[] {
 | 
					    protected getPostsFiles(posts: AddonModForumPost[]): CoreWSFile[] {
 | 
				
			||||||
        let files: CoreWSExternalFile[] = [];
 | 
					        let files: CoreWSFile[] = [];
 | 
				
			||||||
        const getInlineFiles = CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.2');
 | 
					        const getInlineFiles = CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.2');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        posts.forEach((post) => {
 | 
					        posts.forEach((post) => {
 | 
				
			||||||
            if (post.attachments && post.attachments.length) {
 | 
					            if (post.attachments && post.attachments.length) {
 | 
				
			||||||
                files = files.concat(post.attachments as CoreWSExternalFile[]);
 | 
					                files = files.concat(post.attachments as CoreWSFile[]);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (getInlineFiles && post.messageinlinefiles && post.messageinlinefiles.length) {
 | 
					            if (getInlineFiles && post.messageinlinefiles && post.messageinlinefiles.length) {
 | 
				
			||||||
                files = files.concat(post.messageinlinefiles);
 | 
					                files = files.concat(post.messageinlinefiles);
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,7 @@ import { CoreUtils } from '@services/utils/utils';
 | 
				
			|||||||
import { AddonModGlossaryOffline } from './glossary-offline';
 | 
					import { AddonModGlossaryOffline } from './glossary-offline';
 | 
				
			||||||
import { AddonModGlossaryNewEntry, AddonModGlossaryNewEntryWithFiles } from './glossary';
 | 
					import { AddonModGlossaryNewEntry, AddonModGlossaryNewEntryWithFiles } from './glossary';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Helper to gather some common functions for glossary.
 | 
					 * Helper to gather some common functions for glossary.
 | 
				
			||||||
@ -68,7 +68,7 @@ export class AddonModGlossaryHelperProvider {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    hasEntryDataChanged(
 | 
					    hasEntryDataChanged(
 | 
				
			||||||
        entry: AddonModGlossaryNewEntry,
 | 
					        entry: AddonModGlossaryNewEntry,
 | 
				
			||||||
        files: (CoreWSExternalFile | FileEntry)[],
 | 
					        files: CoreFileEntry[],
 | 
				
			||||||
        original?: AddonModGlossaryNewEntryWithFiles,
 | 
					        original?: AddonModGlossaryNewEntryWithFiles,
 | 
				
			||||||
    ): boolean {
 | 
					    ): boolean {
 | 
				
			||||||
        if (!original || typeof original.concept == 'undefined') {
 | 
					        if (!original || typeof original.concept == 'undefined') {
 | 
				
			||||||
@ -98,7 +98,7 @@ export class AddonModGlossaryHelperProvider {
 | 
				
			|||||||
        glossaryId: number,
 | 
					        glossaryId: number,
 | 
				
			||||||
        entryName: string,
 | 
					        entryName: string,
 | 
				
			||||||
        timeCreated: number,
 | 
					        timeCreated: number,
 | 
				
			||||||
        files: (CoreWSExternalFile | FileEntry)[],
 | 
					        files: CoreFileEntry[],
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): Promise<CoreFileUploaderStoreFilesResult> {
 | 
					    ): Promise<CoreFileUploaderStoreFilesResult> {
 | 
				
			||||||
        // Get the folder where to store the files.
 | 
					        // Get the folder where to store the files.
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { ContextLevel } from '@/core/constants';
 | 
					import { ContextLevel } from '@/core/constants';
 | 
				
			||||||
import { Injectable } from '@angular/core';
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file/ngx';
 | 
					 | 
				
			||||||
import { CoreSyncBlockedError } from '@classes/base-sync';
 | 
					import { CoreSyncBlockedError } from '@classes/base-sync';
 | 
				
			||||||
import { CoreNetworkError } from '@classes/errors/network-error';
 | 
					import { CoreNetworkError } from '@classes/errors/network-error';
 | 
				
			||||||
import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync';
 | 
					import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync';
 | 
				
			||||||
@ -24,13 +23,13 @@ import { CoreApp } from '@services/app';
 | 
				
			|||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { CoreSync } from '@services/sync';
 | 
					import { CoreSync } from '@services/sync';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
import { makeSingleton, Translate } from '@singletons';
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
import { CoreEvents } from '@singletons/events';
 | 
					import { CoreEvents } from '@singletons/events';
 | 
				
			||||||
import { AddonModGlossary, AddonModGlossaryProvider } from './glossary';
 | 
					import { AddonModGlossary, AddonModGlossaryProvider } from './glossary';
 | 
				
			||||||
import { AddonModGlossaryHelper } from './glossary-helper';
 | 
					import { AddonModGlossaryHelper } from './glossary-helper';
 | 
				
			||||||
import { AddonModGlossaryOffline, AddonModGlossaryOfflineEntry } from './glossary-offline';
 | 
					import { AddonModGlossaryOffline, AddonModGlossaryOfflineEntry } from './glossary-offline';
 | 
				
			||||||
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
 | 
					import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Service to sync glossaries.
 | 
					 * Service to sync glossaries.
 | 
				
			||||||
@ -309,7 +308,7 @@ export class AddonModGlossarySyncProvider extends CoreCourseActivitySyncBaseProv
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Has some attachments to sync.
 | 
					        // Has some attachments to sync.
 | 
				
			||||||
        let files: (CoreWSExternalFile | FileEntry)[] = entry.attachments.online || [];
 | 
					        let files: CoreFileEntry[] = entry.attachments.online || [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (entry.attachments.offline) {
 | 
					        if (entry.attachments.offline) {
 | 
				
			||||||
            // Has offline files.
 | 
					            // Has offline files.
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,6 @@
 | 
				
			|||||||
// limitations under the License.
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Injectable } from '@angular/core';
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file/ngx';
 | 
					 | 
				
			||||||
import { CoreError } from '@classes/errors/error';
 | 
					import { CoreError } from '@classes/errors/error';
 | 
				
			||||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
					import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
				
			||||||
import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
					import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
				
			||||||
@ -30,6 +29,7 @@ import { makeSingleton, Translate } from '@singletons';
 | 
				
			|||||||
import { AddonModGlossaryEntryDBRecord, ENTRIES_TABLE_NAME } from './database/glossary';
 | 
					import { AddonModGlossaryEntryDBRecord, ENTRIES_TABLE_NAME } from './database/glossary';
 | 
				
			||||||
import { AddonModGlossaryOffline } from './glossary-offline';
 | 
					import { AddonModGlossaryOffline } from './glossary-offline';
 | 
				
			||||||
import { AddonModGlossaryAutoSyncData, AddonModGlossarySyncProvider } from './glossary-sync';
 | 
					import { AddonModGlossaryAutoSyncData, AddonModGlossarySyncProvider } from './glossary-sync';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ROOT_CACHE_KEY = 'mmaModGlossary:';
 | 
					const ROOT_CACHE_KEY = 'mmaModGlossary:';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1431,7 +1431,7 @@ export type AddonModGlossaryNewEntry = {
 | 
				
			|||||||
 * Entry to be added, including attachments.
 | 
					 * Entry to be added, including attachments.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export type AddonModGlossaryNewEntryWithFiles = AddonModGlossaryNewEntry & {
 | 
					export type AddonModGlossaryNewEntryWithFiles = AddonModGlossaryNewEntry & {
 | 
				
			||||||
    files: (CoreWSExternalFile | FileEntry)[];
 | 
					    files: CoreFileEntry[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/c
 | 
				
			|||||||
import { CoreUser } from '@features/user/services/user';
 | 
					import { CoreUser } from '@features/user/services/user';
 | 
				
			||||||
import { CoreFilepool } from '@services/filepool';
 | 
					import { CoreFilepool } from '@services/filepool';
 | 
				
			||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModGlossary, AddonModGlossaryEntry, AddonModGlossaryGlossary, AddonModGlossaryProvider } from '../glossary';
 | 
					import { AddonModGlossary, AddonModGlossaryEntry, AddonModGlossaryGlossary, AddonModGlossaryProvider } from '../glossary';
 | 
				
			||||||
import { AddonModGlossarySync, AddonModGlossarySyncResult } from '../glossary-sync';
 | 
					import { AddonModGlossarySync, AddonModGlossarySyncResult } from '../glossary-sync';
 | 
				
			||||||
@ -38,7 +38,7 @@ export class AddonModGlossaryPrefetchHandlerService extends CoreCourseActivityPr
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
					    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const glossary = await AddonModGlossary.getGlossary(courseId, module.id);
 | 
					            const glossary = await AddonModGlossary.getGlossary(courseId, module.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -68,7 +68,7 @@ export class AddonModGlossaryPrefetchHandlerService extends CoreCourseActivityPr
 | 
				
			|||||||
        module: CoreCourseAnyModuleData,
 | 
					        module: CoreCourseAnyModuleData,
 | 
				
			||||||
        glossary: AddonModGlossaryGlossary,
 | 
					        glossary: AddonModGlossaryGlossary,
 | 
				
			||||||
        entries: AddonModGlossaryEntry[],
 | 
					        entries: AddonModGlossaryEntry[],
 | 
				
			||||||
    ): CoreWSExternalFile[] {
 | 
					    ): CoreWSFile[] {
 | 
				
			||||||
        let files = this.getIntroFilesFromInstance(module, glossary);
 | 
					        let files = this.getIntroFilesFromInstance(module, glossary);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const getInlineFiles = CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.2');
 | 
					        const getInlineFiles = CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.2');
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,7 @@ import { CoreFilepool } from '@services/filepool';
 | 
				
			|||||||
import { CoreNavigator } from '@services/navigator';
 | 
					import { CoreNavigator } from '@services/navigator';
 | 
				
			||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
					import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    AddonModH5PActivity,
 | 
					    AddonModH5PActivity,
 | 
				
			||||||
@ -43,6 +43,7 @@ import {
 | 
				
			|||||||
    AddonModH5PActivitySyncProvider,
 | 
					    AddonModH5PActivitySyncProvider,
 | 
				
			||||||
    AddonModH5PActivitySyncResult,
 | 
					    AddonModH5PActivitySyncResult,
 | 
				
			||||||
} from '../../services/h5pactivity-sync';
 | 
					} from '../../services/h5pactivity-sync';
 | 
				
			||||||
 | 
					import { CoreFileHelper } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component that displays an H5P activity entry page.
 | 
					 * Component that displays an H5P activity entry page.
 | 
				
			||||||
@ -58,7 +59,7 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    h5pActivity?: AddonModH5PActivityData; // The H5P activity object.
 | 
					    h5pActivity?: AddonModH5PActivityData; // The H5P activity object.
 | 
				
			||||||
    accessInfo?: AddonModH5PActivityAccessInfo; // Info about the user capabilities.
 | 
					    accessInfo?: AddonModH5PActivityAccessInfo; // Info about the user capabilities.
 | 
				
			||||||
    deployedFile?: CoreWSExternalFile; // The H5P deployed file.
 | 
					    deployedFile?: CoreWSFile; // The H5P deployed file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    stateMessage?: string; // Message about the file state.
 | 
					    stateMessage?: string; // Message about the file state.
 | 
				
			||||||
    downloading = false; // Whether the H5P file is being downloaded.
 | 
					    downloading = false; // Whether the H5P file is being downloaded.
 | 
				
			||||||
@ -191,10 +192,10 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
 | 
				
			|||||||
            siteId: this.siteId,
 | 
					            siteId: this.siteId,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.fileUrl = this.deployedFile.fileurl;
 | 
					        this.fileUrl = CoreFileHelper.getFileUrl(this.deployedFile);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Listen for changes in the state.
 | 
					        // Listen for changes in the state.
 | 
				
			||||||
        const eventName = await CoreFilepool.getFileEventNameByUrl(this.site.getId(), this.deployedFile.fileurl);
 | 
					        const eventName = await CoreFilepool.getFileEventNameByUrl(this.site.getId(), this.fileUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!this.observer) {
 | 
					        if (!this.observer) {
 | 
				
			||||||
            this.observer = CoreEvents.on(eventName, () => {
 | 
					            this.observer = CoreEvents.on(eventName, () => {
 | 
				
			||||||
@ -213,7 +214,7 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
 | 
				
			|||||||
    protected async calculateFileState(): Promise<void> {
 | 
					    protected async calculateFileState(): Promise<void> {
 | 
				
			||||||
        this.state = await CoreFilepool.getFileStateByUrl(
 | 
					        this.state = await CoreFilepool.getFileStateByUrl(
 | 
				
			||||||
            this.site.getId(),
 | 
					            this.site.getId(),
 | 
				
			||||||
            this.deployedFile!.fileurl,
 | 
					            this.fileUrl!,
 | 
				
			||||||
            this.deployedFile!.timemodified,
 | 
					            this.deployedFile!.timemodified,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -317,7 +318,7 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
 | 
				
			|||||||
        try {
 | 
					        try {
 | 
				
			||||||
            await CoreFilepool.downloadUrl(
 | 
					            await CoreFilepool.downloadUrl(
 | 
				
			||||||
                this.site.getId(),
 | 
					                this.site.getId(),
 | 
				
			||||||
                this.deployedFile!.fileurl,
 | 
					                this.fileUrl!,
 | 
				
			||||||
                false,
 | 
					                false,
 | 
				
			||||||
                this.component,
 | 
					                this.component,
 | 
				
			||||||
                this.componentId,
 | 
					                this.componentId,
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@
 | 
				
			|||||||
import { Injectable } from '@angular/core';
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreWSExternalWarning, CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSExternalWarning, CoreWSExternalFile, CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
					import { CoreTimeUtils } from '@services/utils/time';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
					import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
				
			||||||
@ -305,7 +305,7 @@ export class AddonModH5PActivityProvider {
 | 
				
			|||||||
    async getDeployedFile(
 | 
					    async getDeployedFile(
 | 
				
			||||||
        h5pActivity: AddonModH5PActivityData,
 | 
					        h5pActivity: AddonModH5PActivityData,
 | 
				
			||||||
        options?: AddonModH5PActivityGetDeployedFileOptions,
 | 
					        options?: AddonModH5PActivityGetDeployedFileOptions,
 | 
				
			||||||
    ): Promise<CoreWSExternalFile> {
 | 
					    ): Promise<CoreWSFile> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (h5pActivity.deployedfile) {
 | 
					        if (h5pActivity.deployedfile) {
 | 
				
			||||||
            // File already deployed and still valid, use this one.
 | 
					            // File already deployed and still valid, use this one.
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@ import { CoreH5P } from '@features/h5p/services/h5p';
 | 
				
			|||||||
import { CoreUser } from '@features/user/services/user';
 | 
					import { CoreUser } from '@features/user/services/user';
 | 
				
			||||||
import { CoreFilepool } from '@services/filepool';
 | 
					import { CoreFilepool } from '@services/filepool';
 | 
				
			||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModH5PActivity, AddonModH5PActivityData, AddonModH5PActivityProvider } from '../h5pactivity';
 | 
					import { AddonModH5PActivity, AddonModH5PActivityData, AddonModH5PActivityProvider } from '../h5pactivity';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -39,7 +39,7 @@ export class AddonModH5PActivityPrefetchHandlerService extends CoreCourseActivit
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
					    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const h5pActivity = await AddonModH5PActivity.getH5PActivity(courseId, module.id);
 | 
					        const h5pActivity = await AddonModH5PActivity.getH5PActivity(courseId, module.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ import {
 | 
				
			|||||||
import { CoreFilepool } from '@services/filepool';
 | 
					import { CoreFilepool } from '@services/filepool';
 | 
				
			||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModImscp, AddonModImscpProvider } from '../imscp';
 | 
					import { AddonModImscp, AddonModImscpProvider } from '../imscp';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -58,7 +58,7 @@ export class AddonModImscpPrefetchHandlerService extends CoreCourseResourcePrefe
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
					    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
        // If not found, use undefined so module description is used.
 | 
					        // If not found, use undefined so module description is used.
 | 
				
			||||||
        const imscp = await CoreUtils.ignoreErrors(AddonModImscp.getImscp(courseId, module.id));
 | 
					        const imscp = await CoreUtils.ignoreErrors(AddonModImscp.getImscp(courseId, module.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ import { CoreCourseResourcePrefetchHandlerBase } from '@features/course/classes/
 | 
				
			|||||||
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
 | 
					import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
 | 
				
			||||||
import { CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModLabel, AddonModLabelLabel, AddonModLabelProvider } from '../label';
 | 
					import { AddonModLabel, AddonModLabelLabel, AddonModLabelProvider } from '../label';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -36,7 +36,7 @@ export class AddonModLabelPrefetchHandlerService extends CoreCourseResourcePrefe
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number, ignoreCache?: boolean): Promise<CoreWSExternalFile[]> {
 | 
					    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number, ignoreCache?: boolean): Promise<CoreWSFile[]> {
 | 
				
			||||||
        let label: AddonModLabelLabel | undefined;
 | 
					        let label: AddonModLabelLabel | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (AddonModLabel.isGetLabelAvailableForSite()) {
 | 
					        if (AddonModLabel.isGetLabelAvailableForSite()) {
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ import { CoreGroups } from '@services/groups';
 | 
				
			|||||||
import { CoreFileSizeSum, CorePluginFileDelegate } from '@services/plugin-file-delegate';
 | 
					import { CoreFileSizeSum, CorePluginFileDelegate } from '@services/plugin-file-delegate';
 | 
				
			||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton, ModalController, Translate } from '@singletons';
 | 
					import { makeSingleton, ModalController, Translate } from '@singletons';
 | 
				
			||||||
import { AddonModLessonPasswordModalComponent } from '../../components/password-modal/password-modal';
 | 
					import { AddonModLessonPasswordModalComponent } from '../../components/password-modal/password-modal';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@ -92,7 +92,7 @@ export class AddonModLessonPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
        lesson = passwordData.lesson || lesson;
 | 
					        lesson = passwordData.lesson || lesson;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Get intro files and media files.
 | 
					        // Get intro files and media files.
 | 
				
			||||||
        let files = lesson.mediafiles || [];
 | 
					        let files: CoreWSFile[] = lesson.mediafiles || [];
 | 
				
			||||||
        files = files.concat(this.getIntroFilesFromInstance(module, lesson));
 | 
					        files = files.concat(this.getIntroFilesFromInstance(module, lesson));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const result = await CorePluginFileDelegate.getFilesDownloadSize(files);
 | 
					        const result = await CorePluginFileDelegate.getFilesDownloadSize(files);
 | 
				
			||||||
@ -289,7 +289,8 @@ export class AddonModLessonPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
        const promises: Promise<void>[] = [];
 | 
					        const promises: Promise<void>[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Download intro files and media files.
 | 
					        // Download intro files and media files.
 | 
				
			||||||
        const files = (lesson.mediafiles || []).concat(this.getIntroFilesFromInstance(module, lesson));
 | 
					        let files: CoreWSFile[] = (lesson.mediafiles || []);
 | 
				
			||||||
 | 
					        files = files.concat(this.getIntroFilesFromInstance(module, lesson));
 | 
				
			||||||
        promises.push(CoreFilepool.addFilesToQueue(siteId, files, this.component, module.id));
 | 
					        promises.push(CoreFilepool.addFilesToQueue(siteId, files, this.component, module.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (AddonModLesson.isLessonOffline(lesson)) {
 | 
					        if (AddonModLesson.isLessonOffline(lesson)) {
 | 
				
			||||||
@ -495,7 +496,7 @@ export class AddonModLessonPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Download embedded files in essays.
 | 
					            // Download embedded files in essays.
 | 
				
			||||||
            const files: CoreWSExternalFile[] = [];
 | 
					            const files: CoreWSFile[] = [];
 | 
				
			||||||
            attempt.answerpages.forEach((answerPage) => {
 | 
					            attempt.answerpages.forEach((answerPage) => {
 | 
				
			||||||
                if (!answerPage.page || answerPage.page.qtype != AddonModLessonProvider.LESSON_PAGE_ESSAY) {
 | 
					                if (!answerPage.page || answerPage.page.qtype != AddonModLessonProvider.LESSON_PAGE_ESSAY) {
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
 | 
				
			|||||||
@ -34,6 +34,7 @@ import { AddonModChoiceModule } from './choice/choice.module';
 | 
				
			|||||||
import { AddonModWikiModule } from './wiki/wiki.module';
 | 
					import { AddonModWikiModule } from './wiki/wiki.module';
 | 
				
			||||||
import { AddonModGlossaryModule } from './glossary/glossary.module';
 | 
					import { AddonModGlossaryModule } from './glossary/glossary.module';
 | 
				
			||||||
import { AddonModChatModule } from './chat/chat.module';
 | 
					import { AddonModChatModule } from './chat/chat.module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackModule } from './feedback/feedback.module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@NgModule({
 | 
					@NgModule({
 | 
				
			||||||
    imports: [
 | 
					    imports: [
 | 
				
			||||||
@ -57,6 +58,7 @@ import { AddonModChatModule } from './chat/chat.module';
 | 
				
			|||||||
        AddonModWikiModule,
 | 
					        AddonModWikiModule,
 | 
				
			||||||
        AddonModGlossaryModule,
 | 
					        AddonModGlossaryModule,
 | 
				
			||||||
        AddonModChatModule,
 | 
					        AddonModChatModule,
 | 
				
			||||||
 | 
					        AddonModFeedbackModule,
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class AddonModModule { }
 | 
					export class AddonModModule { }
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ import { CoreFilepool } from '@services/filepool';
 | 
				
			|||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModQuizAccessRuleDelegate } from '../access-rules-delegate';
 | 
					import { AddonModQuizAccessRuleDelegate } from '../access-rules-delegate';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@ -77,7 +77,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet
 | 
				
			|||||||
     * @return Promise resolved with the list of files.
 | 
					     * @return Promise resolved with the list of files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
    async getFiles(module: CoreCourseAnyModuleData, courseId: number, single?: boolean): Promise<CoreWSExternalFile[]> {
 | 
					    async getFiles(module: CoreCourseAnyModuleData, courseId: number, single?: boolean): Promise<CoreWSFile[]> {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const quiz = await AddonModQuiz.getQuiz(courseId, module.id);
 | 
					            const quiz = await AddonModQuiz.getQuiz(courseId, module.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -109,9 +109,9 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet
 | 
				
			|||||||
        quiz: AddonModQuizQuizWSData,
 | 
					        quiz: AddonModQuizQuizWSData,
 | 
				
			||||||
        attempts: AddonModQuizAttemptWSData[],
 | 
					        attempts: AddonModQuizAttemptWSData[],
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): Promise<CoreWSExternalFile[]> {
 | 
					    ): Promise<CoreWSFile[]> {
 | 
				
			||||||
        const getInlineFiles = CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.2');
 | 
					        const getInlineFiles = CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.2');
 | 
				
			||||||
        let files: CoreWSExternalFile[] = [];
 | 
					        let files: CoreWSFile[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await Promise.all(attempts.map(async (attempt) => {
 | 
					        await Promise.all(attempts.map(async (attempt) => {
 | 
				
			||||||
            if (!AddonModQuiz.isAttemptFinished(attempt.state)) {
 | 
					            if (!AddonModQuiz.isAttemptFinished(attempt.state)) {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
 | 
				
			|||||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
 | 
					import { CoreMimetypeUtils } from '@services/utils/mimetype';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
					import { CoreTimeUtils } from '@services/utils/time';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton, Translate } from '@singletons';
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
import { AddonModResourceIndexComponent } from '../../components/index';
 | 
					import { AddonModResourceIndexComponent } from '../../components/index';
 | 
				
			||||||
import { AddonModResource, AddonModResourceCustomData } from '../resource';
 | 
					import { AddonModResource, AddonModResourceCustomData } from '../resource';
 | 
				
			||||||
@ -142,7 +142,7 @@ export class AddonModResourceModuleHandlerService implements CoreCourseModuleHan
 | 
				
			|||||||
        handlerData: CoreCourseModuleHandlerData,
 | 
					        handlerData: CoreCourseModuleHandlerData,
 | 
				
			||||||
    ): Promise<AddonResourceHandlerData> {
 | 
					    ): Promise<AddonResourceHandlerData> {
 | 
				
			||||||
        const promises: Promise<void>[] = [];
 | 
					        const promises: Promise<void>[] = [];
 | 
				
			||||||
        let infoFiles: CoreWSExternalFile[] = [];
 | 
					        let infoFiles: CoreWSFile[] = [];
 | 
				
			||||||
        let options: AddonModResourceCustomData = {};
 | 
					        let options: AddonModResourceCustomData = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Check if the button needs to be shown or not.
 | 
					        // Check if the button needs to be shown or not.
 | 
				
			||||||
@ -166,7 +166,7 @@ export class AddonModResourceModuleHandlerService implements CoreCourseModuleHan
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        await Promise.all(promises);
 | 
					        await Promise.all(promises);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const files: (CoreCourseModuleContentFile | CoreWSExternalFile)[] = module.contents && module.contents.length
 | 
					        const files: (CoreCourseModuleContentFile | CoreWSFile)[] = module.contents && module.contents.length
 | 
				
			||||||
            ? module.contents
 | 
					            ? module.contents
 | 
				
			||||||
            : infoFiles;
 | 
					            : infoFiles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ import { CoreFilepool } from '@services/filepool';
 | 
				
			|||||||
import { CoreFileSizeSum } from '@services/plugin-file-delegate';
 | 
					import { CoreFileSizeSum } from '@services/plugin-file-delegate';
 | 
				
			||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton, Translate } from '@singletons';
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
import { AddonModScorm, AddonModScormProvider, AddonModScormScorm } from '../scorm';
 | 
					import { AddonModScorm, AddonModScormProvider, AddonModScormScorm } from '../scorm';
 | 
				
			||||||
import { AddonModScormSync } from '../scorm-sync';
 | 
					import { AddonModScormSync } from '../scorm-sync';
 | 
				
			||||||
@ -297,7 +297,7 @@ export class AddonModScormPrefetchHandlerService extends CoreCourseActivityPrefe
 | 
				
			|||||||
     * @param single True if we're downloading a single module, false if we're downloading a whole section.
 | 
					     * @param single True if we're downloading a single module, false if we're downloading a whole section.
 | 
				
			||||||
     * @return Promise resolved with the list of files.
 | 
					     * @return Promise resolved with the list of files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
					    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const scorm = await this.getScorm(module, courseId);
 | 
					            const scorm = await this.getScorm(module, courseId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@ import { CoreTextUtils } from '@services/utils/text';
 | 
				
			|||||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
					import { CoreTimeUtils } from '@services/utils/time';
 | 
				
			||||||
import { CoreUrlUtils } from '@services/utils/url';
 | 
					import { CoreUrlUtils } from '@services/utils/url';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWS, CoreWSExternalFile, CoreWSExternalWarning, CoreWSPreSets } from '@services/ws';
 | 
					import { CoreWS, CoreWSExternalFile, CoreWSExternalWarning, CoreWSFile, CoreWSPreSets } from '@services/ws';
 | 
				
			||||||
import { makeSingleton, Translate } from '@singletons';
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
import { CoreEvents } from '@singletons/events';
 | 
					import { CoreEvents } from '@singletons/events';
 | 
				
			||||||
import { AddonModScormOffline } from './scorm-offline';
 | 
					import { AddonModScormOffline } from './scorm-offline';
 | 
				
			||||||
@ -983,8 +983,8 @@ export class AddonModScormProvider {
 | 
				
			|||||||
     * @param scorm SCORM.
 | 
					     * @param scorm SCORM.
 | 
				
			||||||
     * @return File list.
 | 
					     * @return File list.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    getScormFileList(scorm: AddonModScormScorm): CoreWSExternalFile[] {
 | 
					    getScormFileList(scorm: AddonModScormScorm): CoreWSFile[] {
 | 
				
			||||||
        const files: CoreWSExternalFile[] = [];
 | 
					        const files: CoreWSFile[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!this.isScormUnsupported(scorm) && !scorm.warningMessage) {
 | 
					        if (!this.isScormUnsupported(scorm) && !scorm.warningMessage) {
 | 
				
			||||||
            files.push({
 | 
					            files.push({
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,7 @@ import { CoreCourseAnyModuleData } from '@features/course/services/course';
 | 
				
			|||||||
import { CoreFilepool } from '@services/filepool';
 | 
					import { CoreFilepool } from '@services/filepool';
 | 
				
			||||||
import { CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModSurvey, AddonModSurveyProvider } from '../survey';
 | 
					import { AddonModSurvey, AddonModSurveyProvider } from '../survey';
 | 
				
			||||||
import { AddonModSurveySync, AddonModSurveySyncResult } from '../survey-sync';
 | 
					import { AddonModSurveySync, AddonModSurveySyncResult } from '../survey-sync';
 | 
				
			||||||
@ -37,7 +37,7 @@ export class AddonModSurveyPrefetchHandlerService extends CoreCourseActivityPref
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
					    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
 | 
				
			||||||
        const survey = await CoreUtils.ignoreErrors(AddonModSurvey.getSurvey(courseId, module.id));
 | 
					        const survey = await CoreUtils.ignoreErrors(AddonModSurvey.getSurvey(courseId, module.id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.getIntroFilesFromInstance(module, survey);
 | 
					        return this.getIntroFilesFromInstance(module, survey);
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ import { CoreSync } from '@services/sync';
 | 
				
			|||||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { Translate } from '@singletons';
 | 
					import { Translate } from '@singletons';
 | 
				
			||||||
import { CoreEvents } from '@singletons/events';
 | 
					import { CoreEvents } from '@singletons/events';
 | 
				
			||||||
import { CoreForms } from '@singletons/form';
 | 
					import { CoreForms } from '@singletons/form';
 | 
				
			||||||
@ -62,7 +62,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
 | 
				
			|||||||
    protected blockId?: string; // ID to block the subwiki.
 | 
					    protected blockId?: string; // ID to block the subwiki.
 | 
				
			||||||
    protected editing = false; // Whether the user is editing a page (true) or creating a new one (false).
 | 
					    protected editing = false; // Whether the user is editing a page (true) or creating a new one (false).
 | 
				
			||||||
    protected editOffline = false; // Whether the user is editing an offline page.
 | 
					    protected editOffline = false; // Whether the user is editing an offline page.
 | 
				
			||||||
    protected subwikiFiles: CoreWSExternalFile[] = []; // List of files of the subwiki.
 | 
					    protected subwikiFiles: CoreWSFile[] = []; // List of files of the subwiki.
 | 
				
			||||||
    protected originalContent?: string; // The original page content.
 | 
					    protected originalContent?: string; // The original page content.
 | 
				
			||||||
    protected version?: number; // Page version.
 | 
					    protected version?: number; // Page version.
 | 
				
			||||||
    protected renewLockInterval?: number; // An interval to renew the lock every certain time.
 | 
					    protected renewLockInterval?: number; // An interval to renew the lock every certain time.
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,7 @@ import { CoreGroups } from '@services/groups';
 | 
				
			|||||||
import { CoreFileSizeSum, CorePluginFileDelegate } from '@services/plugin-file-delegate';
 | 
					import { CoreFileSizeSum, CorePluginFileDelegate } from '@services/plugin-file-delegate';
 | 
				
			||||||
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonModWiki, AddonModWikiProvider, AddonModWikiSubwikiPage } from '../wiki';
 | 
					import { AddonModWiki, AddonModWikiProvider, AddonModWikiSubwikiPage } from '../wiki';
 | 
				
			||||||
import { AddonModWikiSync, AddonModWikiSyncWikiResult } from '../wiki-sync';
 | 
					import { AddonModWikiSync, AddonModWikiSyncWikiResult } from '../wiki-sync';
 | 
				
			||||||
@ -103,7 +103,7 @@ export class AddonModWikiPrefetchHandlerService extends CoreCourseActivityPrefet
 | 
				
			|||||||
        courseId: number,
 | 
					        courseId: number,
 | 
				
			||||||
        single?: boolean,
 | 
					        single?: boolean,
 | 
				
			||||||
        siteId?: string,
 | 
					        siteId?: string,
 | 
				
			||||||
    ): Promise<CoreWSExternalFile[]> {
 | 
					    ): Promise<CoreWSFile[]> {
 | 
				
			||||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
					        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ import { CoreApp } from '@services/app';
 | 
				
			|||||||
import { CoreNavigator } from '@services/navigator';
 | 
					import { CoreNavigator } from '@services/navigator';
 | 
				
			||||||
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
 | 
					import { CoreWSExternalFile, CoreWSExternalWarning, CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton, Translate } from '@singletons';
 | 
					import { makeSingleton, Translate } from '@singletons';
 | 
				
			||||||
import { CoreEvents } from '@singletons/events';
 | 
					import { CoreEvents } from '@singletons/events';
 | 
				
			||||||
import { AddonModWikiPageDBRecord } from './database/wiki';
 | 
					import { AddonModWikiPageDBRecord } from './database/wiki';
 | 
				
			||||||
@ -196,7 +196,7 @@ export class AddonModWikiProvider {
 | 
				
			|||||||
     * @param options Other options.
 | 
					     * @param options Other options.
 | 
				
			||||||
     * @return Promise resolved with subwiki files.
 | 
					     * @return Promise resolved with subwiki files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getSubwikiFiles(wikiId: number, options: AddonModWikiGetSubwikiFilesOptions = {}): Promise<CoreWSExternalFile[]> {
 | 
					    async getSubwikiFiles(wikiId: number, options: AddonModWikiGetSubwikiFilesOptions = {}): Promise<CoreWSFile[]> {
 | 
				
			||||||
        const site = await CoreSites.getSite(options.siteId);
 | 
					        const site = await CoreSites.getSite(options.siteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const groupId = options.groupId || -1;
 | 
					        const groupId = options.groupId || -1;
 | 
				
			||||||
@ -426,10 +426,10 @@ export class AddonModWikiProvider {
 | 
				
			|||||||
     * @param options Other options.
 | 
					     * @param options Other options.
 | 
				
			||||||
     * @return Promise resolved with the list of files.
 | 
					     * @return Promise resolved with the list of files.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getWikiFileList(wiki: AddonModWikiWiki, options: CoreSitesCommonWSOptions = {}): Promise<CoreWSExternalFile[]> {
 | 
					    async getWikiFileList(wiki: AddonModWikiWiki, options: CoreSitesCommonWSOptions = {}): Promise<CoreWSFile[]> {
 | 
				
			||||||
        options.siteId = options.siteId || CoreSites.getCurrentSiteId();
 | 
					        options.siteId = options.siteId || CoreSites.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let files: CoreWSExternalFile[] = [];
 | 
					        let files: CoreWSFile[] = [];
 | 
				
			||||||
        const modOptions = {
 | 
					        const modOptions = {
 | 
				
			||||||
            cmId: wiki.coursemodule,
 | 
					            cmId: wiki.coursemodule,
 | 
				
			||||||
            ...options, // Include all options.
 | 
					            ...options, // Include all options.
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ import { Injectable, Type } from '@angular/core';
 | 
				
			|||||||
import { CoreQuestion, CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question';
 | 
					import { CoreQuestion, CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question';
 | 
				
			||||||
import { CoreQuestionHandler } from '@features/question/services/question-delegate';
 | 
					import { CoreQuestionHandler } from '@features/question/services/question-delegate';
 | 
				
			||||||
import { CoreQuestionHelper, CoreQuestionQuestion } from '@features/question/services/question-helper';
 | 
					import { CoreQuestionHelper, CoreQuestionQuestion } from '@features/question/services/question-helper';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonQtypeDdMarkerComponent } from '../../component/ddmarker';
 | 
					import { AddonQtypeDdMarkerComponent } from '../../component/ddmarker';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -135,7 +135,7 @@ export class AddonQtypeDdMarkerHandlerService implements CoreQuestionHandler {
 | 
				
			|||||||
     * @param usageId Usage ID.
 | 
					     * @param usageId Usage ID.
 | 
				
			||||||
     * @return List of files or URLs.
 | 
					     * @return List of files or URLs.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    getAdditionalDownloadableFiles(question: CoreQuestionQuestionParsed, usageId?: number): CoreWSExternalFile[] {
 | 
					    getAdditionalDownloadableFiles(question: CoreQuestionQuestionParsed, usageId?: number): CoreWSFile[] {
 | 
				
			||||||
        const treatedQuestion: CoreQuestionQuestion = question;
 | 
					        const treatedQuestion: CoreQuestionQuestion = question;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        CoreQuestionHelper.extractQuestionScripts(treatedQuestion, usageId);
 | 
					        CoreQuestionHelper.extractQuestionScripts(treatedQuestion, usageId);
 | 
				
			||||||
 | 
				
			|||||||
@ -20,9 +20,9 @@ import { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/service
 | 
				
			|||||||
import { AddonModQuizEssayQuestion, CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
 | 
					import { AddonModQuizEssayQuestion, CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
 | 
				
			||||||
import { CoreQuestionHelper } from '@features/question/services/question-helper';
 | 
					import { CoreQuestionHelper } from '@features/question/services/question-helper';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
import { CoreFileSession } from '@services/file-session';
 | 
					import { CoreFileSession } from '@services/file-session';
 | 
				
			||||||
import { CoreQuestion } from '@features/question/services/question';
 | 
					import { CoreQuestion } from '@features/question/services/question';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component to render an essay question.
 | 
					 * Component to render an essay question.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@ -33,7 +33,7 @@ import { CoreQuestion } from '@features/question/services/question';
 | 
				
			|||||||
export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implements OnInit {
 | 
					export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    formControl?: FormControl;
 | 
					    formControl?: FormControl;
 | 
				
			||||||
    attachments?: (CoreWSExternalFile | FileEntry)[];
 | 
					    attachments?: CoreFileEntry[];
 | 
				
			||||||
    uploadFilesSupported = false;
 | 
					    uploadFilesSupported = false;
 | 
				
			||||||
    essayQuestion?: AddonModQuizEssayQuestion;
 | 
					    essayQuestion?: AddonModQuizEssayQuestion;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@ import { CoreSites } from '@services/sites';
 | 
				
			|||||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { AddonQtypeEssayComponent } from '../../component/essay';
 | 
					import { AddonQtypeEssayComponent } from '../../component/essay';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -81,12 +81,12 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler {
 | 
				
			|||||||
     * @param usageId Usage ID.
 | 
					     * @param usageId Usage ID.
 | 
				
			||||||
     * @return List of files or URLs.
 | 
					     * @return List of files or URLs.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    getAdditionalDownloadableFiles(question: CoreQuestionQuestionParsed): CoreWSExternalFile[] {
 | 
					    getAdditionalDownloadableFiles(question: CoreQuestionQuestionParsed): CoreWSFile[] {
 | 
				
			||||||
        if (!question.responsefileareas) {
 | 
					        if (!question.responsefileareas) {
 | 
				
			||||||
            return [];
 | 
					            return [];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return question.responsefileareas.reduce((urlsList, area) => urlsList.concat(area.files || []), <CoreWSExternalFile[]> []);
 | 
					        return question.responsefileareas.reduce((urlsList, area) => urlsList.concat(area.files || []), <CoreWSFile[]> []);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,7 @@
 | 
				
			|||||||
// See the License for the specific language governing permissions and
 | 
					// See the License for the specific language governing permissions and
 | 
				
			||||||
// limitations under the License.
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { COMPILER_OPTIONS, NgModule } from '@angular/core';
 | 
					import { APP_INITIALIZER, COMPILER_OPTIONS, NgModule } from '@angular/core';
 | 
				
			||||||
import { BrowserModule } from '@angular/platform-browser';
 | 
					import { BrowserModule } from '@angular/platform-browser';
 | 
				
			||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 | 
					import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 | 
				
			||||||
import { RouteReuseStrategy } from '@angular/router';
 | 
					import { RouteReuseStrategy } from '@angular/router';
 | 
				
			||||||
@ -29,6 +29,8 @@ import { AddonsModule } from '@/addons/addons.module';
 | 
				
			|||||||
import { AppComponent } from './app.component';
 | 
					import { AppComponent } from './app.component';
 | 
				
			||||||
import { AppRoutingModule } from './app-routing.module';
 | 
					import { AppRoutingModule } from './app-routing.module';
 | 
				
			||||||
import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
 | 
					import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
 | 
				
			||||||
 | 
					import { CoreCronDelegate } from '@services/cron';
 | 
				
			||||||
 | 
					import { CoreSiteInfoCronHandler } from '@services/handlers/site-info-cron';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// For translate loader. AoT requires an exported function for factories.
 | 
					// For translate loader. AoT requires an exported function for factories.
 | 
				
			||||||
export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
 | 
					export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
 | 
				
			||||||
@ -57,6 +59,14 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
 | 
				
			|||||||
        { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
 | 
					        { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
 | 
				
			||||||
        { provide: COMPILER_OPTIONS, useValue: {}, multi: true },
 | 
					        { provide: COMPILER_OPTIONS, useValue: {}, multi: true },
 | 
				
			||||||
        { provide: JitCompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
 | 
					        { provide: JitCompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            provide: APP_INITIALIZER,
 | 
				
			||||||
 | 
					            multi: true,
 | 
				
			||||||
 | 
					            deps: [],
 | 
				
			||||||
 | 
					            useFactory: () => () => {
 | 
				
			||||||
 | 
					                CoreCronDelegate.register(CoreSiteInfoCronHandler.instance);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    bootstrap: [AppComponent],
 | 
					    bootstrap: [AppComponent],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
				
			|||||||
@ -18,11 +18,11 @@ import { FileEntry } from '@ionic-native/file/ngx';
 | 
				
			|||||||
import { CoreFileUploader, CoreFileUploaderTypeList } from '@features/fileuploader/services/fileuploader';
 | 
					import { CoreFileUploader, CoreFileUploaderTypeList } from '@features/fileuploader/services/fileuploader';
 | 
				
			||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
import { Translate } from '@singletons';
 | 
					import { Translate } from '@singletons';
 | 
				
			||||||
import { CoreApp } from '@services/app';
 | 
					import { CoreApp } from '@services/app';
 | 
				
			||||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuploader-helper';
 | 
					import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuploader-helper';
 | 
				
			||||||
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component to render attachments, allow adding more and delete the current ones.
 | 
					 * Component to render attachments, allow adding more and delete the current ones.
 | 
				
			||||||
@ -43,7 +43,7 @@ import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuplo
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class CoreAttachmentsComponent implements OnInit {
 | 
					export class CoreAttachmentsComponent implements OnInit {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Input() files?: (CoreWSExternalFile | FileEntry)[]; // List of attachments. New attachments will be added to this array.
 | 
					    @Input() files?: CoreFileEntry[]; // List of attachments. New attachments will be added to this array.
 | 
				
			||||||
    @Input() maxSize?: number; // Max size for attachments. -1 means unlimited, 0 means user max size, not defined means unknown.
 | 
					    @Input() maxSize?: number; // Max size for attachments. -1 means unlimited, 0 means user max size, not defined means unknown.
 | 
				
			||||||
    @Input() maxSubmissions?: number; // Max number of attachments. -1 means unlimited, not defined means unknown limit.
 | 
					    @Input() maxSubmissions?: number; // Max number of attachments. -1 means unlimited, not defined means unknown limit.
 | 
				
			||||||
    @Input() component?: string; // Component the downloaded files will be linked to.
 | 
					    @Input() component?: string; // Component the downloaded files will be linked to.
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@ import { CoreUtils } from '@services/utils/utils';
 | 
				
			|||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreConstants } from '@/core/constants';
 | 
					import { CoreConstants } from '@/core/constants';
 | 
				
			||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
					import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					import { CoreWSFile } from '@services/ws';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component to handle a remote file. Shows the file name, icon (depending on mimetype) and a button
 | 
					 * Component to handle a remote file. Shows the file name, icon (depending on mimetype) and a button
 | 
				
			||||||
@ -38,7 +38,7 @@ import { CoreWSExternalFile } from '@services/ws';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class CoreFileComponent implements OnInit, OnDestroy {
 | 
					export class CoreFileComponent implements OnInit, OnDestroy {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Input() file?: CoreWSExternalFile; // The file.
 | 
					    @Input() file?: CoreWSFile; // The file.
 | 
				
			||||||
    @Input() component?: string; // Component the file belongs to.
 | 
					    @Input() component?: string; // Component the file belongs to.
 | 
				
			||||||
    @Input() componentId?: string | number; // Component ID.
 | 
					    @Input() componentId?: string | number; // Component ID.
 | 
				
			||||||
    @Input() canDelete?: boolean | string; // Whether file can be deleted.
 | 
					    @Input() canDelete?: boolean | string; // Whether file can be deleted.
 | 
				
			||||||
@ -76,7 +76,7 @@ export class CoreFileComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
        this.alwaysDownload = CoreUtils.isTrueOrOne(this.alwaysDownload);
 | 
					        this.alwaysDownload = CoreUtils.isTrueOrOne(this.alwaysDownload);
 | 
				
			||||||
        this.canDownload = CoreUtils.isTrueOrOne(this.canDownload);
 | 
					        this.canDownload = CoreUtils.isTrueOrOne(this.canDownload);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.fileUrl = this.file.fileurl;
 | 
					        this.fileUrl = CoreFileHelper.getFileUrl(this.file);
 | 
				
			||||||
        this.timemodified = this.file.timemodified || 0;
 | 
					        this.timemodified = this.file.timemodified || 0;
 | 
				
			||||||
        this.siteId = CoreSites.getCurrentSiteId();
 | 
					        this.siteId = CoreSites.getCurrentSiteId();
 | 
				
			||||||
        this.fileSize = this.file.filesize;
 | 
					        this.fileSize = this.file.filesize;
 | 
				
			||||||
@ -88,12 +88,12 @@ export class CoreFileComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        this.showTime = CoreUtils.isTrueOrOne(this.showTime) && this.timemodified > 0;
 | 
					        this.showTime = CoreUtils.isTrueOrOne(this.showTime) && this.timemodified > 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.file.isexternalfile) {
 | 
					        if ('isexternalfile' in this.file && this.file.isexternalfile) {
 | 
				
			||||||
            this.alwaysDownload = true; // Always show the download button in external files.
 | 
					            this.alwaysDownload = true; // Always show the download button in external files.
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.fileIcon = this.file.mimetype ? CoreMimetypeUtils.getMimetypeIcon(this.file.mimetype) :
 | 
					        this.fileIcon = 'mimetype' in this.file && this.file.mimetype ?
 | 
				
			||||||
            CoreMimetypeUtils.getFileIcon(this.fileName);
 | 
					            CoreMimetypeUtils.getMimetypeIcon(this.file.mimetype) : CoreMimetypeUtils.getFileIcon(this.fileName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.canDownload) {
 | 
					        if (this.canDownload) {
 | 
				
			||||||
            this.calculateState();
 | 
					            this.calculateState();
 | 
				
			||||||
 | 
				
			|||||||
@ -13,11 +13,10 @@
 | 
				
			|||||||
// limitations under the License.
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Component, Input, OnInit, DoCheck, KeyValueDiffers } from '@angular/core';
 | 
					import { Component, Input, OnInit, DoCheck, KeyValueDiffers } from '@angular/core';
 | 
				
			||||||
import { FileEntry } from '@ionic-native/file/ngx';
 | 
					import { CoreFileEntry } from '@services/file-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
 | 
					import { CoreMimetypeUtils } from '@services/utils/mimetype';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreWSExternalFile } from '@services/ws';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component to render a file list.
 | 
					 * Component to render a file list.
 | 
				
			||||||
@ -31,7 +30,7 @@ import { CoreWSExternalFile } from '@services/ws';
 | 
				
			|||||||
})
 | 
					})
 | 
				
			||||||
export class CoreFilesComponent implements OnInit, DoCheck {
 | 
					export class CoreFilesComponent implements OnInit, DoCheck {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Input() files?: (CoreWSExternalFile | FileEntry)[]; // List of files.
 | 
					    @Input() files?: CoreFileEntry[]; // List of files.
 | 
				
			||||||
    @Input() component?: string; // Component the downloaded files will be linked to.
 | 
					    @Input() component?: string; // Component the downloaded files will be linked to.
 | 
				
			||||||
    @Input() componentId?: string | number; // Component ID.
 | 
					    @Input() componentId?: string | number; // Component ID.
 | 
				
			||||||
    @Input() alwaysDownload?: boolean | string; // Whether it should always display the refresh button when the file is downloaded.
 | 
					    @Input() alwaysDownload?: boolean | string; // Whether it should always display the refresh button when the file is downloaded.
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,10 @@
 | 
				
			|||||||
 | 
					@import "~theme/globals";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
:host {
 | 
					:host {
 | 
				
			||||||
    .core-input-required-asterisk {
 | 
					    .core-input-required-asterisk {
 | 
				
			||||||
        font-size: 8px;
 | 
					        font-size: 8px;
 | 
				
			||||||
        --padding-start: 4px;
 | 
					 | 
				
			||||||
        line-height: 100%;
 | 
					        line-height: 100%;
 | 
				
			||||||
        vertical-align: top;
 | 
					        vertical-align: top;
 | 
				
			||||||
 | 
					        @include padding-horizontal(4px, null);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user