forked from CIT/Vmeda.Online
		
	
						commit
						6134704fb2
					
				
							
								
								
									
										32
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								.travis.yml
									
									
									
									
									
								
							@ -1,6 +1,5 @@
 | 
			
		||||
os: linux
 | 
			
		||||
dist: trusty
 | 
			
		||||
language: android
 | 
			
		||||
node_js: 12
 | 
			
		||||
 | 
			
		||||
if: env(DEPLOY) = 1
 | 
			
		||||
@ -8,16 +7,6 @@ if: env(DEPLOY) = 1
 | 
			
		||||
git:
 | 
			
		||||
  depth: 3
 | 
			
		||||
 | 
			
		||||
android:
 | 
			
		||||
    components:
 | 
			
		||||
    - tools
 | 
			
		||||
    - platform-tools
 | 
			
		||||
    - build-tools-29.0.3
 | 
			
		||||
    - android-28
 | 
			
		||||
    - extra-google-google_play_services
 | 
			
		||||
    - extra-google-m2repository
 | 
			
		||||
    - extra-android-m2repository
 | 
			
		||||
 | 
			
		||||
before_cache:
 | 
			
		||||
  - rm -rf $HOME/.cache/electron-builder/wine
 | 
			
		||||
  - rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock
 | 
			
		||||
@ -46,3 +35,24 @@ before_script:
 | 
			
		||||
 | 
			
		||||
script:
 | 
			
		||||
  - scripts/build.sh
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  include:
 | 
			
		||||
  - stage: build
 | 
			
		||||
    name: "Build Android"
 | 
			
		||||
    language: android
 | 
			
		||||
    android:
 | 
			
		||||
      components:
 | 
			
		||||
      - tools
 | 
			
		||||
      - platform-tools
 | 
			
		||||
      - build-tools-29.0.3
 | 
			
		||||
      - android-28
 | 
			
		||||
      - extra-google-google_play_services
 | 
			
		||||
      - extra-google-m2repository
 | 
			
		||||
      - extra-android-m2repository
 | 
			
		||||
  - stage: build
 | 
			
		||||
    name: "Build iOS"
 | 
			
		||||
    language: node_js
 | 
			
		||||
    if: env(DEPLOY) = 1 AND env(BUILD_IOS) = 1
 | 
			
		||||
    os: osx
 | 
			
		||||
    osx_image: xcode12.4
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,6 @@ import { AddonCalendarOffline } from './calendar-offline';
 | 
			
		||||
import { AddonCalendarHelper } from './calendar-helper';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { CoreSync } from '@services/sync';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreNetworkError } from '@classes/errors/network-error';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -41,6 +40,8 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider<AddonCalenda
 | 
			
		||||
    static readonly MANUAL_SYNCED = 'addon_calendar_manual_synced';
 | 
			
		||||
    static readonly SYNC_ID = 'calendar';
 | 
			
		||||
 | 
			
		||||
    protected componentTranslatableString = 'addon.calendar.calendarevent';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonCalendarSync');
 | 
			
		||||
    }
 | 
			
		||||
@ -229,12 +230,9 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider<AddonCalenda
 | 
			
		||||
                }));
 | 
			
		||||
 | 
			
		||||
                await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
                // Event deleted, add a warning.
 | 
			
		||||
                result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                    component: Translate.instant('addon.calendar.calendarevent'),
 | 
			
		||||
                    name: data.name,
 | 
			
		||||
                    error: CoreTextUtils.getErrorMessageFromError(error),
 | 
			
		||||
                }));
 | 
			
		||||
                this.addOfflineDataDeletedWarning(result.warnings, data.name, error);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
@ -286,12 +284,9 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider<AddonCalenda
 | 
			
		||||
            result.updated = true;
 | 
			
		||||
 | 
			
		||||
            await AddonCalendarOffline.deleteEvent(event.id!, siteId);
 | 
			
		||||
 | 
			
		||||
            // Event deleted, add a warning.
 | 
			
		||||
            result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                component: Translate.instant('addon.calendar.calendarevent'),
 | 
			
		||||
                name: event.name,
 | 
			
		||||
                error: CoreTextUtils.getErrorMessageFromError(error),
 | 
			
		||||
            }));
 | 
			
		||||
            this.addOfflineDataDeletedWarning(result.warnings, event.name, error);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,6 @@ import { CoreSync } from '@services/sync';
 | 
			
		||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreNetworkError } from '@classes/errors/network-error';
 | 
			
		||||
import { CoreGradesFormattedItem, CoreGradesFormattedRow, CoreGradesHelper } from '@features/grades/services/grades-helper';
 | 
			
		||||
import { AddonModAssignSubmissionDelegate } from './submission-delegate';
 | 
			
		||||
@ -51,11 +50,10 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
    static readonly AUTO_SYNCED = 'addon_mod_assign_autom_synced';
 | 
			
		||||
    static readonly MANUAL_SYNCED = 'addon_mod_assign_manual_synced';
 | 
			
		||||
 | 
			
		||||
    protected componentTranslate: string;
 | 
			
		||||
    protected componentTranslatableString = 'assign';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModLessonSyncProvider');
 | 
			
		||||
        this.componentTranslate = CoreCourse.translateModuleName('assign');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -164,7 +162,6 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
     */
 | 
			
		||||
    async syncAssign(assignId: number, siteId?: string): Promise<AddonModAssignSyncResult> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
        this.componentTranslate = this.componentTranslate || CoreCourse.translateModuleName('assign');
 | 
			
		||||
 | 
			
		||||
        if (this.isSyncing(assignId, siteId)) {
 | 
			
		||||
            // There's already a sync ongoing for this assign, return the promise.
 | 
			
		||||
@ -328,7 +325,6 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
            // The submission was modified in Moodle, discard the submission.
 | 
			
		||||
            this.addOfflineDataDeletedWarning(
 | 
			
		||||
                warnings,
 | 
			
		||||
                this.componentTranslate,
 | 
			
		||||
                assign.name,
 | 
			
		||||
                Translate.instant('addon.mod_assign.warningsubmissionmodified'),
 | 
			
		||||
            );
 | 
			
		||||
@ -369,12 +365,7 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // A WebService has thrown an error, this means it cannot be submitted. Discard the submission.
 | 
			
		||||
            this.addOfflineDataDeletedWarning(
 | 
			
		||||
                warnings,
 | 
			
		||||
                this.componentTranslate,
 | 
			
		||||
                assign.name,
 | 
			
		||||
                CoreTextUtils.getErrorMessageFromError(error) || '',
 | 
			
		||||
            );
 | 
			
		||||
            this.addOfflineDataDeletedWarning(warnings, assign.name, error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Delete the offline data.
 | 
			
		||||
@ -458,7 +449,6 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
            // The submission grade was modified in Moodle, discard it.
 | 
			
		||||
            this.addOfflineDataDeletedWarning(
 | 
			
		||||
                warnings,
 | 
			
		||||
                this.componentTranslate,
 | 
			
		||||
                assign.name,
 | 
			
		||||
                Translate.instant('addon.mod_assign.warningsubmissiongrademodified'),
 | 
			
		||||
            );
 | 
			
		||||
@ -527,12 +517,7 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // A WebService has thrown an error, this means it cannot be submitted. Discard the submission.
 | 
			
		||||
            this.addOfflineDataDeletedWarning(
 | 
			
		||||
                warnings,
 | 
			
		||||
                this.componentTranslate,
 | 
			
		||||
                assign.name,
 | 
			
		||||
                CoreTextUtils.getErrorMessageFromError(error) || '',
 | 
			
		||||
            );
 | 
			
		||||
            this.addOfflineDataDeletedWarning(warnings, assign.name, error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Delete the offline data.
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { ModalController, Translate } from '@singletons';
 | 
			
		||||
import { AddonModForumData, AddonModForumPost, AddonModForumReply } from '@addons/mod/forum/services/forum';
 | 
			
		||||
import { AddonModForumHelper } from '@addons/mod/forum/services/helper';
 | 
			
		||||
import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays a form to edit discussion post.
 | 
			
		||||
 | 
			
		||||
@ -25,10 +25,10 @@ import {
 | 
			
		||||
    AddonModForumNewDiscussionData,
 | 
			
		||||
    AddonModForumReplyDiscussionData,
 | 
			
		||||
} from '@addons/mod/forum/services/forum';
 | 
			
		||||
import { AddonModForumOffline, AddonModForumOfflineDiscussion } from '@addons/mod/forum/services/offline';
 | 
			
		||||
import { AddonModForumOffline, AddonModForumOfflineDiscussion } from '@addons/mod/forum/services/forum-offline';
 | 
			
		||||
import { ModalController, PopoverController, Translate } from '@singletons';
 | 
			
		||||
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
 | 
			
		||||
import { AddonModForumHelper } from '@addons/mod/forum/services/helper';
 | 
			
		||||
import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper';
 | 
			
		||||
import { CoreGroups, CoreGroupsProvider } from '@services/groups';
 | 
			
		||||
import { CoreEvents, CoreEventObserver } from '@singletons/events';
 | 
			
		||||
import {
 | 
			
		||||
@ -36,7 +36,7 @@ import {
 | 
			
		||||
    AddonModForumManualSyncData,
 | 
			
		||||
    AddonModForumSyncProvider,
 | 
			
		||||
    AddonModForumSyncResult,
 | 
			
		||||
} from '@addons/mod/forum/services/sync';
 | 
			
		||||
} from '@addons/mod/forum/services/forum-sync';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreUser } from '@features/user/services/user';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
 | 
			
		||||
@ -44,11 +44,11 @@ import { CoreTag } from '@features/tag/services/tag';
 | 
			
		||||
import { ModalController, PopoverController, Translate } from '@singletons';
 | 
			
		||||
import { CoreFileEntry, CoreFileUploader } from '@features/fileuploader/services/fileuploader';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
import { AddonModForumSync } from '../../services/sync';
 | 
			
		||||
import { AddonModForumSync } from '../../services/forum-sync';
 | 
			
		||||
import { CoreSync } from '@services/sync';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { AddonModForumHelper } from '../../services/helper';
 | 
			
		||||
import { AddonModForumOffline, AddonModForumReplyOptions } from '../../services/offline';
 | 
			
		||||
import { AddonModForumHelper } from '../../services/forum-helper';
 | 
			
		||||
import { AddonModForumOffline, AddonModForumReplyOptions } from '../../services/forum-offline';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { AddonModForumPostOptionsMenuComponent } from '../post-options-menu/post-options-menu';
 | 
			
		||||
import { AddonModForumEditPostComponent } from '../edit-post/edit-post';
 | 
			
		||||
 | 
			
		||||
@ -39,9 +39,9 @@ import { AddonModForumTagAreaHandler } from './services/handlers/tag-area';
 | 
			
		||||
import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate';
 | 
			
		||||
import { AddonModForumPushClickHandler } from './services/handlers/push-click';
 | 
			
		||||
import { AddonModForumProvider } from './services/forum';
 | 
			
		||||
import { AddonModForumOfflineProvider } from './services/offline';
 | 
			
		||||
import { AddonModForumHelperProvider } from './services/helper';
 | 
			
		||||
import { AddonModForumSyncProvider } from './services/sync';
 | 
			
		||||
import { AddonModForumOfflineProvider } from './services/forum-offline';
 | 
			
		||||
import { AddonModForumHelperProvider } from './services/forum-helper';
 | 
			
		||||
import { AddonModForumSyncProvider } from './services/forum-sync';
 | 
			
		||||
 | 
			
		||||
export const ADDON_MOD_FORUM_SERVICES: Type<unknown>[] = [
 | 
			
		||||
    AddonModForumProvider,
 | 
			
		||||
 | 
			
		||||
@ -39,9 +39,9 @@ import {
 | 
			
		||||
    AddonModForumPost,
 | 
			
		||||
    AddonModForumProvider,
 | 
			
		||||
} from '../../services/forum';
 | 
			
		||||
import { AddonModForumHelper } from '../../services/helper';
 | 
			
		||||
import { AddonModForumOffline } from '../../services/offline';
 | 
			
		||||
import { AddonModForumSync, AddonModForumSyncProvider } from '../../services/sync';
 | 
			
		||||
import { AddonModForumHelper } from '../../services/forum-helper';
 | 
			
		||||
import { AddonModForumOffline } from '../../services/forum-offline';
 | 
			
		||||
import { AddonModForumSync, AddonModForumSyncProvider } from '../../services/forum-sync';
 | 
			
		||||
 | 
			
		||||
type SortType = 'flat-newest' | 'flat-oldest' | 'nested';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -26,14 +26,14 @@ import {
 | 
			
		||||
    AddonModForumProvider,
 | 
			
		||||
} from '@addons/mod/forum/services/forum';
 | 
			
		||||
import { CoreEditorRichTextEditorComponent } from '@features/editor/components/rich-text-editor/rich-text-editor';
 | 
			
		||||
import { AddonModForumSync, AddonModForumSyncProvider } from '@addons/mod/forum/services/sync';
 | 
			
		||||
import { AddonModForumSync, AddonModForumSyncProvider } from '@addons/mod/forum/services/forum-sync';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { CoreSync } from '@services/sync';
 | 
			
		||||
import { AddonModForumDiscussionOptions, AddonModForumOffline } from '@addons/mod/forum/services/offline';
 | 
			
		||||
import { AddonModForumDiscussionOptions, AddonModForumOffline } from '@addons/mod/forum/services/forum-offline';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { AddonModForumHelper } from '@addons/mod/forum/services/helper';
 | 
			
		||||
import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper';
 | 
			
		||||
import { IonRefresher } from '@ionic/angular';
 | 
			
		||||
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreSiteSchema } from '@services/sites';
 | 
			
		||||
import { AddonModForumOfflineDiscussion, AddonModForumOfflineReply } from '../offline';
 | 
			
		||||
import { AddonModForumOfflineDiscussion, AddonModForumOfflineReply } from '../forum-offline';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Database variables for AddonModForum service.
 | 
			
		||||
 | 
			
		||||
@ -30,7 +30,7 @@ import {
 | 
			
		||||
    AddonModForumPost,
 | 
			
		||||
    AddonModForumProvider,
 | 
			
		||||
} from './forum';
 | 
			
		||||
import { AddonModForumDiscussionOptions, AddonModForumOffline, AddonModForumOfflineReply } from './offline';
 | 
			
		||||
import { AddonModForumDiscussionOptions, AddonModForumOffline, AddonModForumOfflineReply } from './forum-offline';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service that provides some features for forums.
 | 
			
		||||
@ -14,8 +14,7 @@
 | 
			
		||||
 | 
			
		||||
import { ContextLevel } from '@/core/constants';
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreSyncBaseProvider } from '@classes/base-sync';
 | 
			
		||||
import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync';
 | 
			
		||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
			
		||||
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
 | 
			
		||||
import { CoreRatingSync } from '@features/rating/services/rating-sync';
 | 
			
		||||
@ -23,7 +22,6 @@ import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreGroups } from '@services/groups';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreSync } from '@services/sync';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { CoreArray } from '@singletons/array';
 | 
			
		||||
@ -34,8 +32,8 @@ import {
 | 
			
		||||
    AddonModForumAddDiscussionWSOptionsObject,
 | 
			
		||||
    AddonModForumProvider,
 | 
			
		||||
} from './forum';
 | 
			
		||||
import { AddonModForumHelper } from './helper';
 | 
			
		||||
import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumOfflineReply } from './offline';
 | 
			
		||||
import { AddonModForumHelper } from './forum-helper';
 | 
			
		||||
import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumOfflineReply } from './forum-offline';
 | 
			
		||||
 | 
			
		||||
declare module '@singletons/events' {
 | 
			
		||||
 | 
			
		||||
@ -55,25 +53,17 @@ declare module '@singletons/events' {
 | 
			
		||||
 * Service to sync forums.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModForumSyncProvider extends CoreSyncBaseProvider<AddonModForumSyncResult> {
 | 
			
		||||
export class AddonModForumSyncProvider extends CoreCourseActivitySyncBaseProvider<AddonModForumSyncResult> {
 | 
			
		||||
 | 
			
		||||
    static readonly AUTO_SYNCED = 'addon_mod_forum_autom_synced';
 | 
			
		||||
    static readonly MANUAL_SYNCED = 'addon_mod_forum_manual_synced';
 | 
			
		||||
 | 
			
		||||
    private _componentTranslate?: string;
 | 
			
		||||
    protected componentTranslatableString = 'forum';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModForumSyncProvider');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected get componentTranslate(): string {
 | 
			
		||||
        if (!this._componentTranslate) {
 | 
			
		||||
            this._componentTranslate = CoreCourse.translateModuleName('forum');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this._componentTranslate;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Try to synchronize all the forums in a certain site or in all sites.
 | 
			
		||||
     *
 | 
			
		||||
@ -291,11 +281,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider<AddonModForu
 | 
			
		||||
 | 
			
		||||
                if (errors.length === groupIds.length) {
 | 
			
		||||
                    // All requests failed with WS error.
 | 
			
		||||
                    result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                        component: this.componentTranslate,
 | 
			
		||||
                        name: discussion.name,
 | 
			
		||||
                        error: CoreTextUtils.getErrorMessageFromError(errors[0]),
 | 
			
		||||
                    }));
 | 
			
		||||
                    this.addOfflineDataDeletedWarning(result.warnings, discussion.name, errors[0]);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@ -352,11 +338,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider<AddonModForu
 | 
			
		||||
                promises.push(AddonModForum.getForum(result.itemSet!.courseId!, result.itemSet!.instanceId, { siteId })
 | 
			
		||||
                    .then((forum) => {
 | 
			
		||||
                        result.warnings.forEach((warning) => {
 | 
			
		||||
                            warnings.push(Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                                component: this.componentTranslate,
 | 
			
		||||
                                name: forum.name,
 | 
			
		||||
                                error: warning,
 | 
			
		||||
                            }));
 | 
			
		||||
                            this.addOfflineDataDeletedWarning(warnings, forum.name, warning);
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
                        return;
 | 
			
		||||
@ -512,11 +494,8 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider<AddonModForu
 | 
			
		||||
                    await this.deleteReply(forumId, reply.postid, siteId, userId);
 | 
			
		||||
 | 
			
		||||
                    // Responses deleted, add a warning.
 | 
			
		||||
                    result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                        component: this.componentTranslate,
 | 
			
		||||
                        name: reply.name,
 | 
			
		||||
                        error: CoreTextUtils.getErrorMessageFromError(error),
 | 
			
		||||
                    }));
 | 
			
		||||
                    this.addOfflineDataDeletedWarning(result.warnings, reply.name, error);
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ import { CoreUrlUtils } from '@services/utils/url';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumReplyOptions } from './offline';
 | 
			
		||||
import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumReplyOptions } from './forum-offline';
 | 
			
		||||
 | 
			
		||||
const ROOT_CACHE_KEY = 'mmaModForum:';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,7 @@ import { CoreCourse, CoreCourseAnyModuleData, CoreCourseCommonModWSOptions } fro
 | 
			
		||||
import { CoreUser } from '@features/user/services/user';
 | 
			
		||||
import { CoreGroups, CoreGroupsProvider } from '@services/groups';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { AddonModForumSync } from '../sync';
 | 
			
		||||
import { AddonModForumSync } from '../forum-sync';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreCronHandler } from '@services/cron';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModForumSync } from '../sync';
 | 
			
		||||
import { AddonModForumSync } from '../forum-sync';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Synchronization cron handler.
 | 
			
		||||
 | 
			
		||||
@ -16,15 +16,13 @@ import { Injectable } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
import { CoreNetworkError } from '@classes/errors/network-error';
 | 
			
		||||
import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync';
 | 
			
		||||
import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
			
		||||
import { CoreXAPIOffline } from '@features/xapi/services/offline';
 | 
			
		||||
import { CoreXAPI } from '@features/xapi/services/xapi';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { AddonModH5PActivity, AddonModH5PActivityProvider } from './h5pactivity';
 | 
			
		||||
 | 
			
		||||
@ -36,25 +34,12 @@ export class AddonModH5PActivitySyncProvider extends CoreCourseActivitySyncBaseP
 | 
			
		||||
 | 
			
		||||
    static readonly AUTO_SYNCED = 'addon_mod_h5pactivity_autom_synced';
 | 
			
		||||
 | 
			
		||||
    protected componentTranslate?: string;
 | 
			
		||||
    protected componentTranslatableString = 'h5pactivity';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModH5PActivitySyncProvider');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get component name translated.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Component name translated.
 | 
			
		||||
     */
 | 
			
		||||
    protected getComponentTranslate(): string {
 | 
			
		||||
        if (!this.componentTranslate) {
 | 
			
		||||
            this.componentTranslate = CoreCourse.translateModuleName('h5pactivity');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.componentTranslate;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Try to synchronize all the H5P activities in a certain site or in all sites.
 | 
			
		||||
     *
 | 
			
		||||
@ -188,11 +173,8 @@ export class AddonModH5PActivitySyncProvider extends CoreCourseActivitySyncBaseP
 | 
			
		||||
                await CoreXAPIOffline.deleteStatements(entry.id, siteId);
 | 
			
		||||
 | 
			
		||||
                // Responses deleted, add a warning.
 | 
			
		||||
                result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                    component: this.componentTranslate,
 | 
			
		||||
                    name: entry.extra,
 | 
			
		||||
                    error: CoreTextUtils.getErrorMessageFromError(error),
 | 
			
		||||
                }));
 | 
			
		||||
                this.addOfflineDataDeletedWarning(result.warnings, entry.extra || '', error);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,6 @@ 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 { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
			
		||||
import { CoreUrlUtils } from '@services/utils/url';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
@ -41,7 +40,7 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
 | 
			
		||||
    static readonly AUTO_SYNCED = 'addon_mod_lesson_autom_synced';
 | 
			
		||||
 | 
			
		||||
    protected componentTranslate?: string;
 | 
			
		||||
    protected componentTranslatableString = 'lesson';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModLessonSyncProvider');
 | 
			
		||||
@ -189,7 +188,6 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<AddonModLessonSyncResult> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
        this.componentTranslate = this.componentTranslate || CoreCourse.translateModuleName('lesson');
 | 
			
		||||
 | 
			
		||||
        let syncPromise = this.getOngoingSync(lessonId, siteId);
 | 
			
		||||
        if (syncPromise) {
 | 
			
		||||
@ -317,11 +315,12 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
 | 
			
		||||
        if (attempts.length != attemptsLength) {
 | 
			
		||||
            // Some attempts won't be sent, add a warning.
 | 
			
		||||
            result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                component: this.componentTranslate,
 | 
			
		||||
                name: lesson.name,
 | 
			
		||||
                error: Translate.instant('addon.mod_lesson.warningretakefinished'),
 | 
			
		||||
            }));
 | 
			
		||||
            this.addOfflineDataDeletedWarning(
 | 
			
		||||
                result.warnings,
 | 
			
		||||
                lesson.name,
 | 
			
		||||
                Translate.instant('addon.mod_lesson.warningretakefinished'),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
@ -386,11 +385,7 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
            await AddonModLessonOffline.deleteAttempt(lesson.id, retake, pageId, timemodified, siteId);
 | 
			
		||||
 | 
			
		||||
            // Attempt deleted, add a warning.
 | 
			
		||||
            result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                component: this.componentTranslate,
 | 
			
		||||
                name: lesson.name,
 | 
			
		||||
                error: CoreTextUtils.getErrorMessageFromError(error),
 | 
			
		||||
            }));
 | 
			
		||||
            this.addOfflineDataDeletedWarning(result.warnings, lesson.name, error);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -447,11 +442,11 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
        if (retake.retake != passwordData.accessInfo.attemptscount) {
 | 
			
		||||
            // The retake changed, add a warning if it isn't there already.
 | 
			
		||||
            if (!result.warnings.length) {
 | 
			
		||||
                result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                    component: this.componentTranslate,
 | 
			
		||||
                    name: passwordData.lesson.name,
 | 
			
		||||
                    error: Translate.instant('addon.mod_lesson.warningretakefinished'),
 | 
			
		||||
                }));
 | 
			
		||||
                this.addOfflineDataDeletedWarning(
 | 
			
		||||
                    result.warnings,
 | 
			
		||||
                    passwordData.lesson.name,
 | 
			
		||||
                    Translate.instant('addon.mod_lesson.warningretakefinished'),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await AddonModLessonOffline.deleteRetake(lessonId, siteId);
 | 
			
		||||
@ -488,11 +483,7 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
            await AddonModLessonOffline.deleteRetake(lessonId, siteId);
 | 
			
		||||
 | 
			
		||||
            // Retake deleted, add a warning.
 | 
			
		||||
            result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                component: this.componentTranslate,
 | 
			
		||||
                name: passwordData.lesson.name,
 | 
			
		||||
                error: CoreTextUtils.getErrorMessageFromError(error),
 | 
			
		||||
            }));
 | 
			
		||||
            this.addOfflineDataDeletedWarning(result.warnings, passwordData.lesson.name, error);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ import { AddonModResourceModule } from './resource/resource.module';
 | 
			
		||||
import { AddonModUrlModule } from './url/url.module';
 | 
			
		||||
import { AddonModLtiModule } from './lti/lti.module';
 | 
			
		||||
import { AddonModH5PActivityModule } from './h5pactivity/h5pactivity.module';
 | 
			
		||||
import { AddonModSurveyModule } from './survey/survey.module';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [],
 | 
			
		||||
@ -44,6 +45,7 @@ import { AddonModH5PActivityModule } from './h5pactivity/h5pactivity.module';
 | 
			
		||||
        AddonModImscpModule,
 | 
			
		||||
        AddonModLtiModule,
 | 
			
		||||
        AddonModH5PActivityModule,
 | 
			
		||||
        AddonModSurveyModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [],
 | 
			
		||||
    exports: [],
 | 
			
		||||
 | 
			
		||||
@ -40,7 +40,7 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider
 | 
			
		||||
 | 
			
		||||
    static readonly AUTO_SYNCED = 'addon_mod_quiz_autom_synced';
 | 
			
		||||
 | 
			
		||||
    protected componentTranslate?: string;
 | 
			
		||||
    protected componentTranslatableString = 'quiz';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModQuizSyncProvider');
 | 
			
		||||
@ -271,7 +271,6 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider
 | 
			
		||||
        // Verify that quiz isn't blocked.
 | 
			
		||||
        if (CoreSync.isBlocked(AddonModQuizProvider.COMPONENT, quiz.id, siteId)) {
 | 
			
		||||
            this.logger.debug('Cannot sync quiz ' + quiz.id + ' because it is blocked.');
 | 
			
		||||
            this.componentTranslate = this.componentTranslate || CoreCourse.translateModuleName('quiz');
 | 
			
		||||
 | 
			
		||||
            throw new CoreError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										32
									
								
								src/addons/mod/survey/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/addons/mod/survey/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
// (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 { AddonModSurveyIndexComponent } from './index/index';
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonModSurveyIndexComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreCourseComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModSurveyIndexComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModSurveyComponentsModule {}
 | 
			
		||||
@ -0,0 +1,152 @@
 | 
			
		||||
<!-- 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"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)"
 | 
			
		||||
            [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 safe-area-page">
 | 
			
		||||
 | 
			
		||||
    <core-course-module-description *ngIf="survey && !survey.surveydone && !hasOffline" [description]="description"
 | 
			
		||||
        [component]="component" [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id"
 | 
			
		||||
        [courseId]="courseId">
 | 
			
		||||
    </core-course-module-description>
 | 
			
		||||
 | 
			
		||||
    <!-- Survey already done -->
 | 
			
		||||
    <ion-card class="ion-padding" *ngIf="survey && survey.surveydone">
 | 
			
		||||
        <p class="ion-padding">{{ 'addon.mod_survey.surveycompletednograph' | translate }}</p>
 | 
			
		||||
        <ion-button expand="block" [href]="externalUrl" core-link>
 | 
			
		||||
            <ion-icon name="fas-external-link-alt" slot="start"></ion-icon>
 | 
			
		||||
            {{ 'addon.mod_survey.results' | translate }}
 | 
			
		||||
        </ion-button>
 | 
			
		||||
    </ion-card>
 | 
			
		||||
 | 
			
		||||
    <!-- Survey 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>
 | 
			
		||||
 | 
			
		||||
    <!-- Survey questions -->
 | 
			
		||||
    <form *ngIf="survey && !survey.surveydone && !hasOffline && questions && questions.length">
 | 
			
		||||
 | 
			
		||||
        <ion-grid class="ion-no-padding ion-text-wrap">
 | 
			
		||||
            <ng-container *ngFor="let question of questions; let questionIndex=index; let isEven=even;" class="ion-no-padding ion-text-wrap">
 | 
			
		||||
                <!-- Parent question (Category header) -->
 | 
			
		||||
                <ng-container *ngIf="question.multiArray?.length" >
 | 
			
		||||
                    <h3 class="ion-padding-horizontal" [class.ion-padding-top]="questionIndex == 1">{{ question.text }}</h3>
 | 
			
		||||
                    <ion-row class="ion-align-items-center ion-hide-md-down ion-padding">
 | 
			
		||||
                        <ion-col size="7" class="ion-padding">{{ 'addon.mod_survey.responses' | translate }}</ion-col>
 | 
			
		||||
                        <ion-col size="1" class="ion-text-center option-name"
 | 
			
		||||
                            *ngFor="let option of question.optionsArray; let indexOption=index;">
 | 
			
		||||
                            {{ option }}
 | 
			
		||||
                        </ion-col>
 | 
			
		||||
                    </ion-row>
 | 
			
		||||
                    <ion-item class="ion-text-wrap addon-mod_survey-question" [class.even]="isEven" lines="full">
 | 
			
		||||
                        <ion-label><p>{{ question.intro }}</p></ion-label>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
                </ng-container>
 | 
			
		||||
 | 
			
		||||
                <!-- Subquestion -->
 | 
			
		||||
                <ion-radio-group [(ngModel)]="answers[question.name]" [required]="question.required" [name]="question.name">
 | 
			
		||||
                    <ion-row *ngIf="question.parent !== 0" class="ion-align-items-center ion-padding" [class.even]="isEven">
 | 
			
		||||
 | 
			
		||||
                        <ion-col size="7">
 | 
			
		||||
                            <ion-label id="addon-mod_survey-{{question.id}}">
 | 
			
		||||
                                <span [core-mark-required]="question.required">
 | 
			
		||||
                                    <strong>{{question.num}}.</strong> {{ question.text }}
 | 
			
		||||
                                </span>
 | 
			
		||||
                            </ion-label>
 | 
			
		||||
                        </ion-col>
 | 
			
		||||
 | 
			
		||||
                        <!-- Tablet view: radio buttons -->
 | 
			
		||||
                        <ion-col class="ion-hide-md-down ion-text-center" size="1"
 | 
			
		||||
                            *ngFor="let option of question.optionsArray; let value=index;">
 | 
			
		||||
                            <!-- Empty slot to avoid errors on migration tslint checks -->
 | 
			
		||||
                            <ion-radio [value]="value + 1" [attr.aria-labelledby]="'addon-mod_survey-'+question.id" slot="">
 | 
			
		||||
                            </ion-radio>
 | 
			
		||||
                        </ion-col>
 | 
			
		||||
                        <ion-col class="ion-hide-md-up" size="5">
 | 
			
		||||
                            <ion-select class="ion-padding" [(ngModel)]="answers[question.name]" [required]="question.required"
 | 
			
		||||
                                [attr.aria-labelledby]="'addon-mod_survey-'+question.id" interface="action-sheet"
 | 
			
		||||
                                [name]="question.name">
 | 
			
		||||
                                <ion-select-option value="-1" selected disabled>{{ 'core.choose' | translate }}</ion-select-option>
 | 
			
		||||
                                <ion-select-option *ngFor="let option of question.optionsArray; let value=index;"
 | 
			
		||||
                                    [value]="value +1">
 | 
			
		||||
                                    {{option}}
 | 
			
		||||
                                </ion-select-option>
 | 
			
		||||
                            </ion-select>
 | 
			
		||||
                        </ion-col>
 | 
			
		||||
                    </ion-row>
 | 
			
		||||
                </ion-radio-group>
 | 
			
		||||
 | 
			
		||||
                <!-- Single question (don't belong to a category) -->
 | 
			
		||||
                <ng-container *ngIf="(!question.multiArray || question.multiArray.length == 0) && question.parent === 0">
 | 
			
		||||
                    <ion-row class="ion-align-items-center ion-padding" *ngIf="question.type > 0" [class.even]="isEven">
 | 
			
		||||
                        <ion-col size="7">
 | 
			
		||||
                            <ion-label id="addon-mod_survey-{{question.id}}">
 | 
			
		||||
                                <span [core-mark-required]="question.required">
 | 
			
		||||
                                    <strong>{{question.num}}.</strong> {{ question.text }}
 | 
			
		||||
                                </span>
 | 
			
		||||
                            </ion-label>
 | 
			
		||||
                        </ion-col>
 | 
			
		||||
                        <ion-col size="5">
 | 
			
		||||
                            <ion-select class="ion-padding" [(ngModel)]="answers[question.name]" [required]="question.required"
 | 
			
		||||
                                [attr.aria-labelledby]="'addon-mod_survey-'+question.id" interface="action-sheet"
 | 
			
		||||
                                [name]="question.name">
 | 
			
		||||
                                <ion-select-option *ngFor="let option of question.optionsArray; let value=index;" [value]="value">
 | 
			
		||||
                                    {{option}}
 | 
			
		||||
                                </ion-select-option>
 | 
			
		||||
                            </ion-select>
 | 
			
		||||
                        </ion-col>
 | 
			
		||||
                    </ion-row>
 | 
			
		||||
 | 
			
		||||
                    <ion-item *ngIf="question.type === 0" class="ion-text-wrap" [class.even]="isEven">
 | 
			
		||||
                        <ion-label position="floating" id="addon-mod_survey-{{question.id}}">
 | 
			
		||||
                            <span [core-mark-required]="question.required">
 | 
			
		||||
                                <strong>{{question.num}}.</strong> {{ question.text }}
 | 
			
		||||
                            </span>
 | 
			
		||||
                        </ion-label>
 | 
			
		||||
                        <ion-textarea [(ngModel)]="answers[question.name]" [name]="question.name"
 | 
			
		||||
                            [attr.aria-labelledby]="'addon-mod_survey-'+question.id" [required]="question.required">
 | 
			
		||||
                        </ion-textarea>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
                </ng-container>
 | 
			
		||||
 | 
			
		||||
            </ng-container>
 | 
			
		||||
        </ion-grid>
 | 
			
		||||
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-label>
 | 
			
		||||
                <ion-button expand="block" (click)="submit()" [disabled]="!isValidResponse()">
 | 
			
		||||
                    {{ 'core.submit' | translate }}
 | 
			
		||||
                </ion-button>
 | 
			
		||||
            </ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </form>
 | 
			
		||||
 | 
			
		||||
</core-loading>
 | 
			
		||||
							
								
								
									
										25
									
								
								src/addons/mod/survey/components/index/index.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/addons/mod/survey/components/index/index.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
:host {
 | 
			
		||||
    --grid-background: var(--white);
 | 
			
		||||
    --even-background: var(--gray-light);
 | 
			
		||||
 | 
			
		||||
    .option-name {
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .addon-mod_survey-question {
 | 
			
		||||
        border-top: 1px solid var(--gray);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ion-row {
 | 
			
		||||
        background-color: var(--grid-background);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .even {
 | 
			
		||||
        background-color: var(--even-background);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:host-context(body.dark) {
 | 
			
		||||
    --grid-background: var(--black);
 | 
			
		||||
    --even-background: var(--gray-darker);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										250
									
								
								src/addons/mod/survey/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								src/addons/mod/survey/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,250 @@
 | 
			
		||||
// (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, Optional } from '@angular/core';
 | 
			
		||||
import { CoreIonLoadingElement } from '@classes/ion-loading';
 | 
			
		||||
import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component';
 | 
			
		||||
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
 | 
			
		||||
import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { AddonModSurveyPrefetchHandler } from '../../services/handlers/prefetch';
 | 
			
		||||
import {
 | 
			
		||||
    AddonModSurveyProvider,
 | 
			
		||||
    AddonModSurveySurvey,
 | 
			
		||||
    AddonModSurvey,
 | 
			
		||||
    AddonModSurveySubmitAnswerData,
 | 
			
		||||
} from '../../services/survey';
 | 
			
		||||
import { AddonModSurveyHelper, AddonModSurveyQuestionFormatted } from '../../services/survey-helper';
 | 
			
		||||
import { AddonModSurveyOffline } from '../../services/survey-offline';
 | 
			
		||||
import { AddonModSurveyAutoSyncData, AddonModSurveySync, AddonModSurveySyncResult } from '../../services/survey-sync';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component that displays a survey.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'addon-mod-survey-index',
 | 
			
		||||
    templateUrl: 'addon-mod-survey-index.html',
 | 
			
		||||
    styleUrls: ['index.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    component = AddonModSurveyProvider.COMPONENT;
 | 
			
		||||
    moduleName = 'survey';
 | 
			
		||||
 | 
			
		||||
    survey?: AddonModSurveySurvey;
 | 
			
		||||
    questions: AddonModSurveyQuestionFormatted[] = [];
 | 
			
		||||
    answers: Record<string, string> = {};
 | 
			
		||||
 | 
			
		||||
    protected currentUserId?: number;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected content?: IonContent,
 | 
			
		||||
        @Optional() courseContentsPage?: CoreCourseContentsPage,
 | 
			
		||||
    ) {
 | 
			
		||||
        super('AddonModSurveyIndexComponent', content, courseContentsPage);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        super.ngOnInit();
 | 
			
		||||
 | 
			
		||||
        this.currentUserId = CoreSites.getCurrentSiteUserId();
 | 
			
		||||
 | 
			
		||||
        await this.loadContent(false, true);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await AddonModSurvey.logView(this.survey!.id, this.survey!.name);
 | 
			
		||||
            CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Ignore errors. Just don't check Module completion.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Perform the invalidate content function.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async invalidateContent(): Promise<void> {
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
        promises.push(AddonModSurvey.invalidateSurveyData(this.courseId));
 | 
			
		||||
        if (this.survey) {
 | 
			
		||||
            promises.push(AddonModSurvey.invalidateQuestions(this.survey.id));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compares sync event data with current data to check if refresh content is needed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param syncEventData Data receiven on sync observer.
 | 
			
		||||
     * @return True if refresh is needed, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    protected isRefreshSyncNeeded(syncEventData: AddonModSurveyAutoSyncData): boolean {
 | 
			
		||||
        if (this.survey && syncEventData.surveyId == this.survey.id && syncEventData.userId == this.currentUserId) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download survey contents.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh If it's refreshing content.
 | 
			
		||||
     * @param sync If it should try to sync.
 | 
			
		||||
     * @param showErrors If show errors to the user of hide them.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            this.survey = await AddonModSurvey.getSurvey(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            this.description = this.survey.intro;
 | 
			
		||||
            this.dataRetrieved.emit(this.survey);
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to synchronize the survey.
 | 
			
		||||
                const answersSent = await this.syncActivity(showErrors);
 | 
			
		||||
                if (answersSent) {
 | 
			
		||||
                    // Answers were sent, update the survey.
 | 
			
		||||
                    this.survey = await AddonModSurvey.getSurvey(this.courseId, this.module.id);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Check if there are answers stored in offline.
 | 
			
		||||
            this.hasOffline = this.survey.surveydone
 | 
			
		||||
                ? false
 | 
			
		||||
                : await AddonModSurveyOffline.hasAnswers(this.survey.id);
 | 
			
		||||
 | 
			
		||||
            if (!this.survey.surveydone && !this.hasOffline) {
 | 
			
		||||
                await this.fetchQuestions();
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convenience function to get survey questions.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchQuestions(): Promise<void> {
 | 
			
		||||
        const questions = await AddonModSurvey.getQuestions(this.survey!.id, { cmId: this.module.id });
 | 
			
		||||
 | 
			
		||||
        this.questions = AddonModSurveyHelper.formatQuestions(questions);
 | 
			
		||||
 | 
			
		||||
        // Init answers object.
 | 
			
		||||
        this.questions.forEach((question) => {
 | 
			
		||||
            if (question.name) {
 | 
			
		||||
                const isTextArea = question.multiArray && question.multiArray.length === 0 && question.type === 0;
 | 
			
		||||
                this.answers[question.name] = question.required ? '-1' : (isTextArea ? '' : '0');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (question.multiArray && !question.multiArray.length && question.parent === 0 && question.type > 0) {
 | 
			
		||||
                // Options shown in a select. Remove all HTML.
 | 
			
		||||
                question.optionsArray = question.optionsArray?.map((option) => CoreTextUtils.cleanTags(option));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if answers are valid to be submitted.
 | 
			
		||||
     *
 | 
			
		||||
     * @return If answers are valid
 | 
			
		||||
     */
 | 
			
		||||
    isValidResponse(): boolean {
 | 
			
		||||
        return !this.questions.some((question) => question.required && question.name &&
 | 
			
		||||
            (question.type === 0 ? this.answers[question.name] == '' : parseInt(this.answers[question.name], 10) === -1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save options selected.
 | 
			
		||||
     */
 | 
			
		||||
    async submit(): Promise<void> {
 | 
			
		||||
        let modal: CoreIonLoadingElement | undefined;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreDomUtils.showConfirm(Translate.instant('core.areyousure'));
 | 
			
		||||
 | 
			
		||||
            const answers: AddonModSurveySubmitAnswerData[] = [];
 | 
			
		||||
            modal = await CoreDomUtils.showModalLoading('core.sending', true);
 | 
			
		||||
 | 
			
		||||
            for (const x in this.answers) {
 | 
			
		||||
                answers.push({
 | 
			
		||||
                    key: x,
 | 
			
		||||
                    value: this.answers[x],
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const online = await AddonModSurvey.submitAnswers(this.survey!.id, this.survey!.name, this.courseId, answers);
 | 
			
		||||
 | 
			
		||||
            CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: this.moduleName });
 | 
			
		||||
 | 
			
		||||
            if (online && this.isPrefetched()) {
 | 
			
		||||
                // The survey is downloaded, update the data.
 | 
			
		||||
                try {
 | 
			
		||||
                    await AddonModSurveySync.prefetchAfterUpdate(
 | 
			
		||||
                        AddonModSurveyPrefetchHandler.instance,
 | 
			
		||||
                        this.module,
 | 
			
		||||
                        this.courseId,
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    // Update the view.
 | 
			
		||||
                    this.showLoadingAndFetch(false, false);
 | 
			
		||||
                } catch {
 | 
			
		||||
                    // Prefetch failed, refresh the data.
 | 
			
		||||
                    await this.showLoadingAndRefresh(false);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Not downloaded, refresh the data.
 | 
			
		||||
                await this.showLoadingAndRefresh(false);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, 'addon.mod_survey.cannotsubmitsurvey', true);
 | 
			
		||||
        } finally {
 | 
			
		||||
            modal?.dismiss();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Performs the sync of the activity.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async sync(): Promise<void> {
 | 
			
		||||
        await AddonModSurveySync.syncSurvey(this.survey!.id, this.currentUserId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if sync has succeed from result sync data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param result Data returned on the sync function.
 | 
			
		||||
     * @return If suceed or not.
 | 
			
		||||
     */
 | 
			
		||||
    protected hasSyncSucceed(result: AddonModSurveySyncResult): boolean {
 | 
			
		||||
        return result.answersSent;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								src/addons/mod/survey/lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/addons/mod/survey/lang.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
{
 | 
			
		||||
    "cannotsubmitsurvey": "Sorry, there was a problem submitting your survey. Please try again.",
 | 
			
		||||
    "errorgetsurvey": "Error getting survey data.",
 | 
			
		||||
    "ifoundthat": "I found that",
 | 
			
		||||
    "ipreferthat": "I prefer that",
 | 
			
		||||
    "modulenameplural": "Surveys",
 | 
			
		||||
    "responses": "Responses",
 | 
			
		||||
    "results": "Results",
 | 
			
		||||
    "surveycompletednograph": "You have completed this survey."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/addons/mod/survey/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/addons/mod/survey/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
<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"></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-survey-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)"></addon-mod-survey-index>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										30
									
								
								src/addons/mod/survey/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/addons/mod/survey/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
// (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, ViewChild } from '@angular/core';
 | 
			
		||||
import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page';
 | 
			
		||||
import { AddonModSurveyIndexComponent } from '../../components/index';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays a survey.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-addon-mod-survey-index',
 | 
			
		||||
    templateUrl: 'index.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModSurveyIndexPage extends CoreCourseModuleMainActivityPage<AddonModSurveyIndexComponent> {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(AddonModSurveyIndexComponent) activityComponent?: AddonModSurveyIndexComponent;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								src/addons/mod/survey/services/database/survey.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/addons/mod/survey/services/database/survey.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
			
		||||
// (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 AddonModSurveyOfflineProvider.
 | 
			
		||||
 */
 | 
			
		||||
export const SURVEY_TABLE = 'addon_mod_survey_answers';
 | 
			
		||||
export const ADDON_MOD_SURVEY_OFFLINE_SITE_SCHEMA: CoreSiteSchema = {
 | 
			
		||||
    name: 'AddonModSurveyOfflineProvider',
 | 
			
		||||
    version: 1,
 | 
			
		||||
    tables: [
 | 
			
		||||
        {
 | 
			
		||||
            name: SURVEY_TABLE,
 | 
			
		||||
            columns: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'surveyid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'name',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'courseid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'userid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'answers',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'timecreated',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            primaryKeys: ['surveyid', 'userid'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Survey offline answers.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModSurveyAnswersDBRecord = {
 | 
			
		||||
    surveyid: number;
 | 
			
		||||
    userid: number;
 | 
			
		||||
    name: string;
 | 
			
		||||
    courseid: number;
 | 
			
		||||
    answers: string;
 | 
			
		||||
    timecreated: number;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										32
									
								
								src/addons/mod/survey/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/addons/mod/survey/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
// (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';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to treat links to survey.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class AddonModSurveyIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModSurveyLinkHandler';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModSurvey', 'survey');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModSurveyIndexLinkHandler = makeSingleton(AddonModSurveyIndexLinkHandlerService);
 | 
			
		||||
							
								
								
									
										32
									
								
								src/addons/mod/survey/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/addons/mod/survey/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
// (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';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to treat links to survey list page.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class AddonModSurveyListLinkHandlerService extends CoreContentLinksModuleListHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModSurveyListLinkHandler';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModSurvey', 'survey');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModSurveyListLinkHandler = makeSingleton(AddonModSurveyListLinkHandlerService);
 | 
			
		||||
							
								
								
									
										84
									
								
								src/addons/mod/survey/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/addons/mod/survey/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 { AddonModSurveyIndexComponent } from '../../components/index';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to support survey modules.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class AddonModSurveyModuleHandlerService implements CoreCourseModuleHandler {
 | 
			
		||||
 | 
			
		||||
    static readonly PAGE_NAME = 'mod_survey';
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModSurvey';
 | 
			
		||||
    modName = 'survey';
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getData(
 | 
			
		||||
        module: CoreCourseAnyModuleData,
 | 
			
		||||
    ): CoreCourseModuleHandlerData {
 | 
			
		||||
        return {
 | 
			
		||||
            icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined),
 | 
			
		||||
            title: module.name,
 | 
			
		||||
            class: 'addon-mod_survey-handler',
 | 
			
		||||
            showDownloadButton: true,
 | 
			
		||||
            action: (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions) => {
 | 
			
		||||
                options = options || {};
 | 
			
		||||
                options.params = options.params || {};
 | 
			
		||||
                Object.assign(options.params, { module });
 | 
			
		||||
                const routeParams = '/' + courseId + '/' + module.id;
 | 
			
		||||
 | 
			
		||||
                CoreNavigator.navigateToSitePath(AddonModSurveyModuleHandlerService.PAGE_NAME + routeParams, options);
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async getMainComponent(): Promise<Type<unknown>> {
 | 
			
		||||
        return AddonModSurveyIndexComponent;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModSurveyModuleHandler = makeSingleton(AddonModSurveyModuleHandlerService);
 | 
			
		||||
							
								
								
									
										114
									
								
								src/addons/mod/survey/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/addons/mod/survey/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,114 @@
 | 
			
		||||
// (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 } from '@features/course/services/course';
 | 
			
		||||
import { CoreFilepool } from '@services/filepool';
 | 
			
		||||
import { CoreSitesReadingStrategy } from '@services/sites';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreWSExternalFile } from '@services/ws';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModSurvey, AddonModSurveyProvider } from '../survey';
 | 
			
		||||
import { AddonModSurveySync, AddonModSurveySyncResult } from '../survey-sync';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to prefetch surveys.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class AddonModSurveyPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModSurvey';
 | 
			
		||||
    modName = 'survey';
 | 
			
		||||
    component = AddonModSurveyProvider.COMPONENT;
 | 
			
		||||
    updatesNames = /^configuration$|^.*files$|^answers$/;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
			
		||||
        const survey = await CoreUtils.ignoreErrors(AddonModSurvey.getSurvey(courseId, module.id));
 | 
			
		||||
 | 
			
		||||
        return this.getIntroFilesFromInstance(module, survey);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateContent(moduleId: number, courseId: number): Promise<void> {
 | 
			
		||||
        return AddonModSurvey.invalidateContent(moduleId, courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateModule(module: CoreCourseAnyModuleData, courseId: number): Promise<void> {
 | 
			
		||||
        await AddonModSurvey.invalidateSurveyData(courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    prefetch(module: CoreCourseAnyModuleData, courseId?: number): Promise<void> {
 | 
			
		||||
        return this.prefetchPackage(module, courseId, this.prefetchSurvey.bind(this, module, courseId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Prefetch a survey.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module Module.
 | 
			
		||||
     * @param courseId Course ID the module belongs to.
 | 
			
		||||
     * @param siteId SiteId or current site.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async prefetchSurvey(module: CoreCourseAnyModuleData, courseId: number, siteId: string): Promise<void> {
 | 
			
		||||
        const survey = await AddonModSurvey.getSurvey(courseId, module.id, {
 | 
			
		||||
            readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
 | 
			
		||||
            siteId,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const promises: Promise<unknown>[] = [];
 | 
			
		||||
        const files = this.getIntroFilesFromInstance(module, survey);
 | 
			
		||||
 | 
			
		||||
        // Prefetch files.
 | 
			
		||||
        promises.push(CoreFilepool.addFilesToQueue(siteId, files, AddonModSurveyProvider.COMPONENT, module.id));
 | 
			
		||||
 | 
			
		||||
        // If survey isn't answered, prefetch the questions.
 | 
			
		||||
        if (!survey.surveydone) {
 | 
			
		||||
            promises.push(AddonModSurvey.getQuestions(survey.id, {
 | 
			
		||||
                cmId: module.id,
 | 
			
		||||
                readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
 | 
			
		||||
                siteId,
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    sync(module: CoreCourseAnyModuleData, courseId: number, siteId?: string): Promise<AddonModSurveySyncResult> {
 | 
			
		||||
        return AddonModSurveySync.syncSurvey(module.instance!, undefined, siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModSurveyPrefetchHandler = makeSingleton(AddonModSurveyPrefetchHandlerService);
 | 
			
		||||
							
								
								
									
										43
									
								
								src/addons/mod/survey/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/addons/mod/survey/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreCronHandler } from '@services/cron';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModSurveySync } from '../survey-sync';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Synchronization cron handler.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class AddonModSurveySyncCronHandlerService implements CoreCronHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModSurveySyncCronHandler';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async execute(siteId?: string, force?: boolean): Promise<void> {
 | 
			
		||||
        await AddonModSurveySync.syncAllSurveys(siteId, force);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getInterval(): number {
 | 
			
		||||
        return AddonModSurveySync.syncInterval;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModSurveySyncCronHandler = makeSingleton(AddonModSurveySyncCronHandlerService);
 | 
			
		||||
							
								
								
									
										138
									
								
								src/addons/mod/survey/services/survey-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/addons/mod/survey/services/survey-helper.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,138 @@
 | 
			
		||||
// (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 { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { AddonModSurveyQuestion } from './survey';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service that provides helper functions for surveys.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class AddonModSurveyHelperProvider {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Turns a string with values separated by commas into an array.
 | 
			
		||||
     *
 | 
			
		||||
     * @param value Value to convert.
 | 
			
		||||
     * @return Array.
 | 
			
		||||
     */
 | 
			
		||||
    protected commaStringToArray(value: string | string[]): string[] {
 | 
			
		||||
        if (typeof value == 'string') {
 | 
			
		||||
            if (value.length > 0) {
 | 
			
		||||
                return value.split(',');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the parent questions and puts them in an object: ID -> question.
 | 
			
		||||
     *
 | 
			
		||||
     * @param questions Questions.
 | 
			
		||||
     * @return Object with parent questions.
 | 
			
		||||
     */
 | 
			
		||||
    protected getParentQuestions(questions: AddonModSurveyQuestion[]): {[id: number]: AddonModSurveyQuestion} {
 | 
			
		||||
        const parents: { [id: number]: AddonModSurveyQuestion } = {};
 | 
			
		||||
 | 
			
		||||
        questions.forEach((question) => {
 | 
			
		||||
            if (question.parent === 0) {
 | 
			
		||||
                parents[question.id] = question;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return parents;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Format a questions list, turning "multi" and "options" strings into arrays and adding the properties
 | 
			
		||||
     * 'num' and 'name'.
 | 
			
		||||
     *
 | 
			
		||||
     * @param questions Questions.
 | 
			
		||||
     * @return Promise resolved with the formatted questions.
 | 
			
		||||
     */
 | 
			
		||||
    formatQuestions(questions: AddonModSurveyQuestion[]): AddonModSurveyQuestionFormatted[] {
 | 
			
		||||
        const strIPreferThat = Translate.instant('addon.mod_survey.ipreferthat');
 | 
			
		||||
        const strIFoundThat = Translate.instant('addon.mod_survey.ifoundthat');
 | 
			
		||||
        const strChoose = Translate.instant('core.choose');
 | 
			
		||||
 | 
			
		||||
        const formatted: AddonModSurveyQuestionFormatted[] = [];
 | 
			
		||||
        const parents = this.getParentQuestions(questions);
 | 
			
		||||
 | 
			
		||||
        let num = 1;
 | 
			
		||||
 | 
			
		||||
        questions.forEach((question) => {
 | 
			
		||||
            // Copy the object to prevent modifying the original.
 | 
			
		||||
            const q1: AddonModSurveyQuestionFormatted = Object.assign({}, question);
 | 
			
		||||
            const parent = parents[q1.parent];
 | 
			
		||||
 | 
			
		||||
            // Turn multi and options into arrays.
 | 
			
		||||
            q1.multiArray = this.commaStringToArray(q1.multi);
 | 
			
		||||
            q1.optionsArray = this.commaStringToArray(q1.options);
 | 
			
		||||
 | 
			
		||||
            if (parent) {
 | 
			
		||||
                // It's a sub-question.
 | 
			
		||||
                q1.required = true;
 | 
			
		||||
 | 
			
		||||
                if (parent.type === 1 || parent.type === 2) {
 | 
			
		||||
                    // One answer question. Set its name and add it to the returned array.
 | 
			
		||||
                    q1.name = 'q' + (parent.type == 2 ? 'P' : '') + q1.id;
 | 
			
		||||
                    q1.num = num++;
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Two answers per question (COLLES P&A). We'll add two questions.
 | 
			
		||||
                    const q2 = Object.assign({}, q1);
 | 
			
		||||
 | 
			
		||||
                    q1.text = strIPreferThat + ' ' + q1.text;
 | 
			
		||||
                    q1.name = 'qP' + q1.id;
 | 
			
		||||
                    q1.num = num++;
 | 
			
		||||
                    formatted.push(q1);
 | 
			
		||||
 | 
			
		||||
                    q2.text = strIFoundThat + ' ' + q2.text;
 | 
			
		||||
                    q2.name = 'q' + q1.id;
 | 
			
		||||
                    q2.num = num++;
 | 
			
		||||
                    formatted.push(q2);
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            } else if (q1.multiArray && q1.multiArray.length === 0) {
 | 
			
		||||
                // It's a single question.
 | 
			
		||||
                q1.name = 'q' + q1.id;
 | 
			
		||||
                q1.num = num++;
 | 
			
		||||
                if (q1.type > 0) { // Add "choose" option since this question is not required.
 | 
			
		||||
                    q1.optionsArray.unshift(strChoose);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            formatted.push(q1);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return formatted;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModSurveyHelper = makeSingleton(AddonModSurveyHelperProvider);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Survey question with some calculated data.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModSurveyQuestionFormatted = AddonModSurveyQuestion & {
 | 
			
		||||
    required?: boolean; // Calculated in the app. Whether the question is required.
 | 
			
		||||
    name?: string; // Calculated in the app. The name of the question.
 | 
			
		||||
    num?: number; // Calculated in the app. Number of the question.
 | 
			
		||||
    multiArray?: string[]; // Subquestions ids, converted to an array.
 | 
			
		||||
    optionsArray?: string[]; // Question options, converted to an array.
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										151
									
								
								src/addons/mod/survey/services/survey-offline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								src/addons/mod/survey/services/survey-offline.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,151 @@
 | 
			
		||||
// (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 { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModSurveyAnswersDBRecord, SURVEY_TABLE } from './database/survey';
 | 
			
		||||
import { AddonModSurveySubmitAnswerData } from './survey';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to handle Offline survey.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class AddonModSurveyOfflineProvider {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a survey answers.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @param userId User the answers belong to. If not defined, current user in site.
 | 
			
		||||
     * @return Promise resolved if deleted, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteSurveyAnswers(surveyId: number, siteId?: string, userId?: number): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
        userId = userId || site.getUserId();
 | 
			
		||||
 | 
			
		||||
        await site.getDb().deleteRecords(SURVEY_TABLE, { surveyid: surveyId, userid: userId });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all the stored data from all the surveys.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with answers.
 | 
			
		||||
     */
 | 
			
		||||
    async getAllData(siteId?: string): Promise<AddonModSurveyAnswersDBRecordFormatted[]> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
        const entries = await site.getDb().getAllRecords<AddonModSurveyAnswersDBRecord>(SURVEY_TABLE);
 | 
			
		||||
 | 
			
		||||
        return entries.map((entry) => Object.assign(entry, {
 | 
			
		||||
            answers: CoreTextUtils.parseJSON<AddonModSurveySubmitAnswerData[]>(entry.answers),
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a survey stored answers.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @param userId User the answers belong to. If not defined, current user in site.
 | 
			
		||||
     * @return Promise resolved with the answers.
 | 
			
		||||
     */
 | 
			
		||||
    async getSurveyAnswers(surveyId: number, siteId?: string, userId?: number): Promise<AddonModSurveySubmitAnswerData[]> {
 | 
			
		||||
        try {
 | 
			
		||||
            const entry = await this.getSurveyData(surveyId, siteId, userId);
 | 
			
		||||
 | 
			
		||||
            return entry.answers || [];
 | 
			
		||||
        } catch {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a survey stored data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @param userId User the answers belong to. If not defined, current user in site.
 | 
			
		||||
     * @return Promise resolved with the data.
 | 
			
		||||
     */
 | 
			
		||||
    async getSurveyData(surveyId: number, siteId?: string, userId?: number): Promise<AddonModSurveyAnswersDBRecordFormatted> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
        userId = userId || site.getUserId();
 | 
			
		||||
 | 
			
		||||
        const entry = await site.getDb().getRecord<AddonModSurveyAnswersDBRecord>(
 | 
			
		||||
            SURVEY_TABLE,
 | 
			
		||||
            { surveyid: surveyId, userid: userId },
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return Object.assign(entry, {
 | 
			
		||||
            answers: CoreTextUtils.parseJSON<AddonModSurveySubmitAnswerData[]>(entry.answers),
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if there are offline answers to send.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @param userId User the answers belong to. If not defined, current user in site.
 | 
			
		||||
     * @return Promise resolved with boolean: true if has offline answers, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async hasAnswers(surveyId: number, siteId?: string, userId?: number): Promise<boolean> {
 | 
			
		||||
        const answers = await this.getSurveyAnswers(surveyId, siteId, userId);
 | 
			
		||||
 | 
			
		||||
        return !!answers.length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save answers to be sent later.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param name Survey name.
 | 
			
		||||
     * @param courseId Course ID the survey belongs to.
 | 
			
		||||
     * @param answers Answers.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @param userId User the answers belong to. If not defined, current user in site.
 | 
			
		||||
     * @return Promise resolved if stored, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async saveAnswers(
 | 
			
		||||
        surveyId: number,
 | 
			
		||||
        name: string,
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        answers: AddonModSurveySubmitAnswerData[],
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
        userId?: number,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
        userId = userId || site.getUserId();
 | 
			
		||||
 | 
			
		||||
        const entry: AddonModSurveyAnswersDBRecord = {
 | 
			
		||||
            surveyid: surveyId,
 | 
			
		||||
            name: name,
 | 
			
		||||
            courseid: courseId,
 | 
			
		||||
            userid: userId,
 | 
			
		||||
            answers: JSON.stringify(answers),
 | 
			
		||||
            timecreated: new Date().getTime(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await site.getDb().insertRecord(SURVEY_TABLE, entry);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModSurveyOffline = makeSingleton(AddonModSurveyOfflineProvider);
 | 
			
		||||
 | 
			
		||||
export type AddonModSurveyAnswersDBRecordFormatted = Omit<AddonModSurveyAnswersDBRecord, 'answers'> & {
 | 
			
		||||
    answers: AddonModSurveySubmitAnswerData[];
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										251
									
								
								src/addons/mod/survey/services/survey-sync.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								src/addons/mod/survey/services/survey-sync.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,251 @@
 | 
			
		||||
// (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 { CoreNetworkError } from '@classes/errors/network-error';
 | 
			
		||||
import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync';
 | 
			
		||||
import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { AddonModSurveyPrefetchHandler } from './handlers/prefetch';
 | 
			
		||||
import { AddonModSurvey, AddonModSurveyProvider } from './survey';
 | 
			
		||||
import { AddonModSurveyAnswersDBRecordFormatted, AddonModSurveyOffline } from './survey-offline';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to sync surveys.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvider<AddonModSurveySyncResult> {
 | 
			
		||||
 | 
			
		||||
    static readonly AUTO_SYNCED = 'addon_mod_survey_autom_synced';
 | 
			
		||||
 | 
			
		||||
    protected componentTranslatableString = 'survey';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModSurveySyncProvider');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the ID of a survey sync.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param userId User the answers belong to.
 | 
			
		||||
     * @return Sync ID.
 | 
			
		||||
     * @protected
 | 
			
		||||
     */
 | 
			
		||||
    getSyncId(surveyId: number, userId: number): string {
 | 
			
		||||
        return surveyId + '#' + userId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Try to synchronize all the surveys 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.
 | 
			
		||||
     */
 | 
			
		||||
    syncAllSurveys(siteId?: string, force?: boolean): Promise<void> {
 | 
			
		||||
        return this.syncOnSites('all surveys', this.syncAllSurveysFunc.bind(this, !!force), siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sync all pending surveys on a site.
 | 
			
		||||
     *
 | 
			
		||||
     * @param force Wether to force sync not depending on last execution.
 | 
			
		||||
     * @param siteId Site ID to sync.
 | 
			
		||||
     * @param Promise resolved if sync is successful, rejected if sync fails.
 | 
			
		||||
     */
 | 
			
		||||
    protected async syncAllSurveysFunc(force: boolean, siteId: string): Promise<void> {
 | 
			
		||||
        // Get all survey answers pending to be sent in the site.
 | 
			
		||||
        const entries = await AddonModSurveyOffline.getAllData(siteId);
 | 
			
		||||
 | 
			
		||||
        // Sync all surveys.
 | 
			
		||||
        const promises = entries.map(async (entry) => {
 | 
			
		||||
            const result = await (force
 | 
			
		||||
                ? this.syncSurvey(entry.surveyid, entry.userid, siteId)
 | 
			
		||||
                : this.syncSurveyIfNeeded(entry.surveyid, entry.userid, siteId));
 | 
			
		||||
 | 
			
		||||
            if (result && result.answersSent) {
 | 
			
		||||
                // Sync successful, send event.
 | 
			
		||||
                CoreEvents.trigger(AddonModSurveySyncProvider.AUTO_SYNCED, {
 | 
			
		||||
                    surveyId: entry.surveyid,
 | 
			
		||||
                    userId: entry.userid,
 | 
			
		||||
                    warnings: result.warnings,
 | 
			
		||||
                }, siteId);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sync a survey only if a certain time has passed since the last time.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param userId User the answers belong to.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the survey is synced or if it doesn't need to be synced.
 | 
			
		||||
     */
 | 
			
		||||
    async syncSurveyIfNeeded(surveyId: number, userId: number, siteId?: string): Promise<AddonModSurveySyncResult | undefined> {
 | 
			
		||||
        const syncId = this.getSyncId(surveyId, userId);
 | 
			
		||||
 | 
			
		||||
        const needed = await this.isSyncNeeded(syncId, siteId);
 | 
			
		||||
        if (needed) {
 | 
			
		||||
            return this.syncSurvey(surveyId, userId, siteId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Synchronize a survey.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param userId User the answers belong to. If not defined, current user.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async syncSurvey(surveyId: number, userId?: number, siteId?: string): Promise<AddonModSurveySyncResult> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
        siteId = site.getId();
 | 
			
		||||
        userId = userId || site.getUserId();
 | 
			
		||||
 | 
			
		||||
        const syncId = this.getSyncId(surveyId, userId);
 | 
			
		||||
 | 
			
		||||
        if (this.isSyncing(syncId, siteId)) {
 | 
			
		||||
            // There's already a sync ongoing for this site, return the promise.
 | 
			
		||||
            return this.getOngoingSync(syncId, siteId)!;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.logger.debug(`Try to sync survey '${surveyId}' for user '${userId}'`);
 | 
			
		||||
 | 
			
		||||
        // Get offline events.
 | 
			
		||||
        const syncPromise = this.performSyncSurvey(surveyId, userId, siteId);
 | 
			
		||||
 | 
			
		||||
        return this.addOngoingSync(syncId, syncPromise, siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Perform the survey sync.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param userId User the answers belong to. If not defined, current user.
 | 
			
		||||
     * @param siteId Site ID.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    protected async performSyncSurvey(surveyId: number, userId: number, siteId: string): Promise<AddonModSurveySyncResult> {
 | 
			
		||||
        const result: AddonModSurveySyncResult = {
 | 
			
		||||
            warnings: [],
 | 
			
		||||
            answersSent: false,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Sync offline logs.
 | 
			
		||||
        CoreUtils.ignoreErrors(CoreCourseLogHelper.syncActivity(AddonModSurveyProvider.COMPONENT, surveyId, siteId));
 | 
			
		||||
 | 
			
		||||
        let answersNumber = 0;
 | 
			
		||||
        let data: AddonModSurveyAnswersDBRecordFormatted | undefined;
 | 
			
		||||
        try {
 | 
			
		||||
            // Get answers to be sent.
 | 
			
		||||
            data = await AddonModSurveyOffline.getSurveyData(surveyId, siteId, userId);
 | 
			
		||||
 | 
			
		||||
            answersNumber = data.answers.length;
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (answersNumber > 0 && data) {
 | 
			
		||||
            if (!CoreApp.isOnline()) {
 | 
			
		||||
                // Cannot sync in offline.
 | 
			
		||||
                throw new CoreNetworkError();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            result.courseId = data.courseid;
 | 
			
		||||
 | 
			
		||||
            // Send the answers.
 | 
			
		||||
            try {
 | 
			
		||||
                await AddonModSurvey.submitAnswersOnline(surveyId, data.answers, siteId);
 | 
			
		||||
 | 
			
		||||
                result.answersSent = true;
 | 
			
		||||
 | 
			
		||||
                // Answers sent, delete them.
 | 
			
		||||
                await AddonModSurveyOffline.deleteSurveyAnswers(surveyId, siteId, userId);
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                if (!CoreUtils.isWebServiceError(error)) {
 | 
			
		||||
                    // Local error, reject.
 | 
			
		||||
                    throw error;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // The WebService has thrown an error, this means that answers cannot be submitted. Delete them.
 | 
			
		||||
                result.answersSent = true;
 | 
			
		||||
 | 
			
		||||
                await AddonModSurveyOffline.deleteSurveyAnswers(surveyId, siteId, userId);
 | 
			
		||||
 | 
			
		||||
                // Answers deleted, add a warning.
 | 
			
		||||
                this.addOfflineDataDeletedWarning(result.warnings, data.name, error);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (result.courseId) {
 | 
			
		||||
                await AddonModSurvey.invalidateSurveyData(result.courseId, siteId);
 | 
			
		||||
 | 
			
		||||
                // Data has been sent to server, update survey data.
 | 
			
		||||
                const module = await CoreCourse.getModuleBasicInfoByInstance(surveyId, 'survey', siteId);
 | 
			
		||||
 | 
			
		||||
                CoreUtils.ignoreErrors(
 | 
			
		||||
                    this.prefetchAfterUpdate(AddonModSurveyPrefetchHandler.instance, module, result.courseId, undefined, siteId),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const syncId = this.getSyncId(surveyId, userId);
 | 
			
		||||
        // Sync finished, set sync time.
 | 
			
		||||
        CoreUtils.ignoreErrors(this.setSyncTime(syncId, siteId));
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModSurveySync = makeSingleton(AddonModSurveySyncProvider);
 | 
			
		||||
 | 
			
		||||
declare module '@singletons/events' {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Augment CoreEventsData interface with events specific to this service.
 | 
			
		||||
     *
 | 
			
		||||
     * @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
 | 
			
		||||
     */
 | 
			
		||||
    export interface CoreEventsData {
 | 
			
		||||
        [AddonModSurveySyncProvider.AUTO_SYNCED]: AddonModSurveyAutoSyncData;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by a assign sync.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModSurveySyncResult = {
 | 
			
		||||
    warnings: string[]; // List of warnings.
 | 
			
		||||
    answersSent: boolean; // Whether some data was sent to the server or offline data was updated.
 | 
			
		||||
    courseId?: number; // Course the survey belongs to (if known).
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data passed to AUTO_SYNCED event.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModSurveyAutoSyncData = {
 | 
			
		||||
    surveyId: number;
 | 
			
		||||
    warnings: string[];
 | 
			
		||||
    userId: number;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										393
									
								
								src/addons/mod/survey/services/survey.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								src/addons/mod/survey/services/survey.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,393 @@
 | 
			
		||||
// (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 { CoreError } from '@classes/errors/error';
 | 
			
		||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
			
		||||
import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreFilepool } from '@services/filepool';
 | 
			
		||||
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModSurveyOffline } from './survey-offline';
 | 
			
		||||
 | 
			
		||||
const ROOT_CACHE_KEY = 'mmaModSurvey:';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service that provides some features for surveys.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class AddonModSurveyProvider {
 | 
			
		||||
 | 
			
		||||
    static readonly COMPONENT = 'mmaModSurvey';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a survey's questions.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param options Other options.
 | 
			
		||||
     * @return Promise resolved when the questions are retrieved.
 | 
			
		||||
     */
 | 
			
		||||
    async getQuestions(surveyId: number, options: CoreCourseCommonModWSOptions = {}): Promise<AddonModSurveyQuestion[]> {
 | 
			
		||||
        const site = await CoreSites.getSite(options.siteId);
 | 
			
		||||
 | 
			
		||||
        const params: AddonModSurveyGetQuestionsWSParams = {
 | 
			
		||||
            surveyid: surveyId,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const preSets: CoreSiteWSPreSets = {
 | 
			
		||||
            cacheKey: this.getQuestionsCacheKey(surveyId),
 | 
			
		||||
            updateFrequency: CoreSite.FREQUENCY_RARELY,
 | 
			
		||||
            component: AddonModSurveyProvider.COMPONENT,
 | 
			
		||||
            componentId: options.cmId,
 | 
			
		||||
            ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const response = await site.read<AddonModSurveyGetQuestionsWSResponse>('mod_survey_get_questions', params, preSets);
 | 
			
		||||
        if (response.questions) {
 | 
			
		||||
            return response.questions;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new CoreError('No questions were found.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get cache key for survey questions WS calls.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @return Cache key.
 | 
			
		||||
     */
 | 
			
		||||
    protected getQuestionsCacheKey(surveyId: number): string {
 | 
			
		||||
        return ROOT_CACHE_KEY + 'questions:' + surveyId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get cache key for survey data WS calls.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @return Cache key.
 | 
			
		||||
     */
 | 
			
		||||
    protected getSurveyCacheKey(courseId: number): string {
 | 
			
		||||
        return ROOT_CACHE_KEY + 'survey:' + courseId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a survey data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param key Name of the property to check.
 | 
			
		||||
     * @param value Value to search.
 | 
			
		||||
     * @param options Other options.
 | 
			
		||||
     * @return Promise resolved when the survey is retrieved.
 | 
			
		||||
     */
 | 
			
		||||
    protected async getSurveyDataByKey(
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        key: string,
 | 
			
		||||
        value: number,
 | 
			
		||||
        options: CoreSitesCommonWSOptions = {},
 | 
			
		||||
    ): Promise<AddonModSurveySurvey> {
 | 
			
		||||
        const site = await CoreSites.getSite(options.siteId);
 | 
			
		||||
 | 
			
		||||
        const params: AddonModSurveyGetSurveysByCoursesWSParams = {
 | 
			
		||||
            courseids: [courseId],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const preSets: CoreSiteWSPreSets = {
 | 
			
		||||
            cacheKey: this.getSurveyCacheKey(courseId),
 | 
			
		||||
            updateFrequency: CoreSite.FREQUENCY_RARELY,
 | 
			
		||||
            component: AddonModSurveyProvider.COMPONENT,
 | 
			
		||||
            ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const response =
 | 
			
		||||
            await site.read<AddonModSurveyGetSurveysByCoursesWSResponse>('mod_survey_get_surveys_by_courses', params, preSets);
 | 
			
		||||
 | 
			
		||||
        const currentSurvey = response.surveys.find((survey) => survey[key] == value);
 | 
			
		||||
        if (currentSurvey) {
 | 
			
		||||
            return currentSurvey;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new CoreError('Activity not found.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a survey by course module ID.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param cmId Course module ID.
 | 
			
		||||
     * @param options Other options.
 | 
			
		||||
     * @return Promise resolved when the survey is retrieved.
 | 
			
		||||
     */
 | 
			
		||||
    getSurvey(courseId: number, cmId: number, options: CoreSitesCommonWSOptions = {}): Promise<AddonModSurveySurvey> {
 | 
			
		||||
        return this.getSurveyDataByKey(courseId, 'coursemodule', cmId, options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a survey by ID.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param id Survey ID.
 | 
			
		||||
     * @param options Other options.
 | 
			
		||||
     * @return Promise resolved when the survey is retrieved.
 | 
			
		||||
     */
 | 
			
		||||
    getSurveyById(courseId: number, id: number, options: CoreSitesCommonWSOptions = {}): Promise<AddonModSurveySurvey> {
 | 
			
		||||
        return this.getSurveyDataByKey(courseId, 'id', id, options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidate the prefetched content.
 | 
			
		||||
     *
 | 
			
		||||
     * @param moduleId The module ID.
 | 
			
		||||
     * @param courseId Course ID of the module.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the data is invalidated.
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise<void> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
        promises.push(this.getSurvey(courseId, moduleId).then(async (survey) => {
 | 
			
		||||
            const ps: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
            // Do not invalidate activity data before getting activity info, we need it!
 | 
			
		||||
            ps.push(this.invalidateSurveyData(courseId, siteId));
 | 
			
		||||
            ps.push(this.invalidateQuestions(survey.id, siteId));
 | 
			
		||||
 | 
			
		||||
            await Promise.all(ps);
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        promises.push(CoreFilepool.invalidateFilesByComponent(siteId, AddonModSurveyProvider.COMPONENT, moduleId));
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.allPromises(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidates survey questions.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the data is invalidated.
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateQuestions(surveyId: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await site.invalidateWsCacheForKey(this.getQuestionsCacheKey(surveyId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidates survey data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the data is invalidated.
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateSurveyData(courseId: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await site.invalidateWsCacheForKey(this.getSurveyCacheKey(courseId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Report the survey as being viewed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id Module ID.
 | 
			
		||||
     * @param name Name of the assign.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the WS call is successful.
 | 
			
		||||
     */
 | 
			
		||||
    async logView(id: number, name?: string, siteId?: string): Promise<void> {
 | 
			
		||||
        const params: AddonModSurveyViewSurveyWSParams = {
 | 
			
		||||
            surveyid: id,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await CoreCourseLogHelper.logSingle(
 | 
			
		||||
            'mod_survey_view_survey',
 | 
			
		||||
            params,
 | 
			
		||||
            AddonModSurveyProvider.COMPONENT,
 | 
			
		||||
            id,
 | 
			
		||||
            name,
 | 
			
		||||
            'survey',
 | 
			
		||||
            {},
 | 
			
		||||
            siteId,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send survey answers. If cannot send them to Moodle, they'll be stored in offline to be sent later.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param name Survey name.
 | 
			
		||||
     * @param courseId Course ID the survey belongs to.
 | 
			
		||||
     * @param answers Answers.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with boolean if success: true if answers were sent to server,
 | 
			
		||||
     *         false if stored in device.
 | 
			
		||||
     */
 | 
			
		||||
    async submitAnswers(
 | 
			
		||||
        surveyId: number,
 | 
			
		||||
        name: string,
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        answers: AddonModSurveySubmitAnswerData[],
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<boolean> {
 | 
			
		||||
 | 
			
		||||
        // Convenience function to store a survey to be synchronized later.
 | 
			
		||||
        const storeOffline = async (): Promise<boolean> => {
 | 
			
		||||
            await AddonModSurveyOffline.saveAnswers(surveyId, name, courseId, answers, siteId);
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        if (!CoreApp.isOnline()) {
 | 
			
		||||
            // App is offline, store the message.
 | 
			
		||||
            return storeOffline();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            // If there's already answers to be sent to the server, discard it first.
 | 
			
		||||
            await AddonModSurveyOffline.deleteSurveyAnswers(surveyId, siteId);
 | 
			
		||||
 | 
			
		||||
            // Device is online, try to send them to server.
 | 
			
		||||
            await this.submitAnswersOnline(surveyId, answers, siteId);
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (CoreUtils.isWebServiceError(error)) {
 | 
			
		||||
                // It's a WebService error, the user cannot send the message so don't store it.
 | 
			
		||||
                throw error;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return storeOffline();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send survey answers to Moodle.
 | 
			
		||||
     *
 | 
			
		||||
     * @param surveyId Survey ID.
 | 
			
		||||
     * @param answers Answers.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when answers are successfully submitted.
 | 
			
		||||
     */
 | 
			
		||||
    async submitAnswersOnline(surveyId: number, answers: AddonModSurveySubmitAnswerData[], siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const params: AddonModSurveySubmitAnswersWSParams = {
 | 
			
		||||
            surveyid: surveyId,
 | 
			
		||||
            answers: answers,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const response = await site.write<CoreStatusWithWarningsWSResponse>('mod_survey_submit_answers', params);
 | 
			
		||||
        if (!response.status) {
 | 
			
		||||
            throw new CoreError('Error submitting answers.');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModSurvey = makeSingleton(AddonModSurveyProvider);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of mod_survey_view_survey WS.
 | 
			
		||||
 */
 | 
			
		||||
type AddonModSurveyViewSurveyWSParams = {
 | 
			
		||||
    surveyid: number; // Survey instance id.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Survey returned by WS mod_survey_get_surveys_by_courses.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModSurveySurvey = {
 | 
			
		||||
    id: number; // Survey id.
 | 
			
		||||
    coursemodule: number; // Course module id.
 | 
			
		||||
    course: number; // Course id.
 | 
			
		||||
    name: string; // Survey name.
 | 
			
		||||
    intro?: string; // The Survey intro.
 | 
			
		||||
    introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | 
			
		||||
    introfiles?: CoreWSExternalFile[]; // @since 3.2.
 | 
			
		||||
    template?: number; // Survey type.
 | 
			
		||||
    days?: number; // Days.
 | 
			
		||||
    questions?: string; // Question ids.
 | 
			
		||||
    surveydone?: number; // Did I finish the survey?.
 | 
			
		||||
    timecreated?: number; // Time of creation.
 | 
			
		||||
    timemodified?: number; // Time of last modification.
 | 
			
		||||
    section?: number; // Course section id.
 | 
			
		||||
    visible?: number; // Visible.
 | 
			
		||||
    groupmode?: number; // Group mode.
 | 
			
		||||
    groupingid?: number; // Group id.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Survey question.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModSurveyQuestion = {
 | 
			
		||||
    id: number; // Question id.
 | 
			
		||||
    text: string; // Question text.
 | 
			
		||||
    shorttext: string; // Question short text.
 | 
			
		||||
    multi: string; // Subquestions ids.
 | 
			
		||||
    intro: string; // The question intro.
 | 
			
		||||
    type: number; // Question type.
 | 
			
		||||
    options: string; // Question options.
 | 
			
		||||
    parent: number; // Parent question (for subquestions).
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of mod_survey_get_questions WS.
 | 
			
		||||
 */
 | 
			
		||||
type AddonModSurveyGetQuestionsWSParams = {
 | 
			
		||||
    surveyid: number; // Survey instance id.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by mod_survey_get_questions WS.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModSurveyGetQuestionsWSResponse = {
 | 
			
		||||
    questions: AddonModSurveyQuestion[];
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of mod_survey_get_surveys_by_courses WS.
 | 
			
		||||
 */
 | 
			
		||||
type AddonModSurveyGetSurveysByCoursesWSParams = {
 | 
			
		||||
    courseids?: number[]; // Array of course ids.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by mod_survey_get_surveys_by_courses WS.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModSurveyGetSurveysByCoursesWSResponse = {
 | 
			
		||||
    surveys: AddonModSurveySurvey[];
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AddonModSurveySubmitAnswerData = {
 | 
			
		||||
    key: string; // Answer key.
 | 
			
		||||
    value: string; // Answer value.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of mod_survey_submit_answers WS.
 | 
			
		||||
 */
 | 
			
		||||
type AddonModSurveySubmitAnswersWSParams = {
 | 
			
		||||
    surveyid: number; // Survey id.
 | 
			
		||||
    answers: AddonModSurveySubmitAnswerData[];
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										39
									
								
								src/addons/mod/survey/survey-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/addons/mod/survey/survey-lazy.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 { NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { AddonModSurveyIndexPage } from './pages/index';
 | 
			
		||||
import { AddonModSurveyComponentsModule } from './components/components.module';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: ':courseId/:cmId',
 | 
			
		||||
        component: AddonModSurveyIndexPage,
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        AddonModSurveyComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonModSurveyIndexPage,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModSurveyLazyModule {}
 | 
			
		||||
							
								
								
									
										75
									
								
								src/addons/mod/survey/survey.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/addons/mod/survey/survey.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -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 { 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 { CoreCronDelegate } from '@services/cron';
 | 
			
		||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
 | 
			
		||||
import { AddonModSurveyComponentsModule } from './components/components.module';
 | 
			
		||||
import { ADDON_MOD_SURVEY_OFFLINE_SITE_SCHEMA } from './services/database/survey';
 | 
			
		||||
import { AddonModSurveyIndexLinkHandler } from './services/handlers/index-link';
 | 
			
		||||
import { AddonModSurveyListLinkHandler } from './services/handlers/list-link';
 | 
			
		||||
import { AddonModSurveyModuleHandler, AddonModSurveyModuleHandlerService } from './services/handlers/module';
 | 
			
		||||
import { AddonModSurveyPrefetchHandler } from './services/handlers/prefetch';
 | 
			
		||||
import { AddonModSurveySyncCronHandler } from './services/handlers/sync-cron';
 | 
			
		||||
import { AddonModSurveyProvider } from './services/survey';
 | 
			
		||||
import { AddonModSurveyHelperProvider } from './services/survey-helper';
 | 
			
		||||
import { AddonModSurveyOfflineProvider } from './services/survey-offline';
 | 
			
		||||
import { AddonModSurveySyncProvider } from './services/survey-sync';
 | 
			
		||||
 | 
			
		||||
// List of providers (without handlers).
 | 
			
		||||
export const ADDON_MOD_SURVEY_SERVICES: Type<unknown>[] = [
 | 
			
		||||
    AddonModSurveyProvider,
 | 
			
		||||
    AddonModSurveyHelperProvider,
 | 
			
		||||
    AddonModSurveySyncProvider,
 | 
			
		||||
    AddonModSurveyOfflineProvider,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: AddonModSurveyModuleHandlerService.PAGE_NAME,
 | 
			
		||||
        loadChildren: () => import('./survey-lazy.module').then(m => m.AddonModSurveyLazyModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreMainMenuTabRoutingModule.forChild(routes),
 | 
			
		||||
        AddonModSurveyComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
            provide: CORE_SITE_SCHEMAS,
 | 
			
		||||
            useValue: [ADDON_MOD_SURVEY_OFFLINE_SITE_SCHEMA],
 | 
			
		||||
            multi: true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            provide: APP_INITIALIZER,
 | 
			
		||||
            multi: true,
 | 
			
		||||
            deps: [],
 | 
			
		||||
            useFactory: () => () => {
 | 
			
		||||
                CoreCourseModuleDelegate.registerHandler(AddonModSurveyModuleHandler.instance);
 | 
			
		||||
                CoreCourseModulePrefetchDelegate.registerHandler(AddonModSurveyPrefetchHandler.instance);
 | 
			
		||||
                CoreCronDelegate.register(AddonModSurveySyncCronHandler.instance);
 | 
			
		||||
                CoreContentLinksDelegate.registerHandler(AddonModSurveyIndexLinkHandler.instance);
 | 
			
		||||
                CoreContentLinksDelegate.registerHandler(AddonModSurveyListLinkHandler.instance);
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModSurveyModule {}
 | 
			
		||||
@ -19,7 +19,7 @@ import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { CoreLogger } from '@singletons/logger';
 | 
			
		||||
import { CoreError } from '@classes/errors/error';
 | 
			
		||||
import { CoreAnyError, CoreError } from '@classes/errors/error';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Blocked sync error.
 | 
			
		||||
@ -41,6 +41,16 @@ export class CoreSyncBaseProvider<T = void> {
 | 
			
		||||
     */
 | 
			
		||||
    component = 'core';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Translatable component name string.
 | 
			
		||||
     */
 | 
			
		||||
    protected componentTranslatableString = 'generic component';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Translated name of the component.
 | 
			
		||||
     */
 | 
			
		||||
    protected componentTranslateInternal?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sync provider's interval.
 | 
			
		||||
     */
 | 
			
		||||
@ -58,15 +68,14 @@ export class CoreSyncBaseProvider<T = void> {
 | 
			
		||||
     * Add an offline data deleted warning to a list of warnings.
 | 
			
		||||
     *
 | 
			
		||||
     * @param warnings List of warnings.
 | 
			
		||||
     * @param component Component.
 | 
			
		||||
     * @param name Instance name.
 | 
			
		||||
     * @param error Specific error message.
 | 
			
		||||
     */
 | 
			
		||||
    protected addOfflineDataDeletedWarning(warnings: string[], component: string, name: string, error: string): void {
 | 
			
		||||
    protected addOfflineDataDeletedWarning(warnings: string[], name: string, error: CoreAnyError): void {
 | 
			
		||||
        const warning = Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
            component: component,
 | 
			
		||||
            component: this.componentTranslate,
 | 
			
		||||
            name: name,
 | 
			
		||||
            error: error,
 | 
			
		||||
            error: CoreTextUtils.getErrorMessageFromError(error),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (warnings.indexOf(warning) == -1) {
 | 
			
		||||
@ -304,4 +313,17 @@ export class CoreSyncBaseProvider<T = void> {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get component name translated.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Component name translated.
 | 
			
		||||
     */
 | 
			
		||||
    protected get componentTranslate(): string {
 | 
			
		||||
        if (!this.componentTranslateInternal) {
 | 
			
		||||
            this.componentTranslateInternal = Translate.instant(this.componentTranslatableString);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.componentTranslateInternal!;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,8 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreTextErrorObject } from '@services/utils/text';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Base Error class.
 | 
			
		||||
 *
 | 
			
		||||
@ -31,3 +33,5 @@ export class CoreError extends Error {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type CoreAnyError = string | CoreError | CoreTextErrorObject | null | undefined;
 | 
			
		||||
 | 
			
		||||
@ -137,7 +137,7 @@ import { ADDON_MOD_PAGE_SERVICES } from '@addons/mod/page/page.module';
 | 
			
		||||
import { ADDON_MOD_QUIZ_SERVICES } from '@addons/mod/quiz/quiz.module';
 | 
			
		||||
import { ADDON_MOD_RESOURCE_SERVICES } from '@addons/mod/resource/resource.module';
 | 
			
		||||
// @todo import { ADDON_MOD_SCORM_SERVICES } from '@addons/mod/scorm/scorm.module';
 | 
			
		||||
// @todo import { ADDON_MOD_SURVEY_SERVICES } from '@addons/mod/survey/survey.module';
 | 
			
		||||
import { ADDON_MOD_SURVEY_SERVICES } from '@addons/mod/survey/survey.module';
 | 
			
		||||
import { ADDON_MOD_URL_SERVICES } from '@addons/mod/url/url.module';
 | 
			
		||||
// @todo import { ADDON_MOD_WIKI_SERVICES } from '@addons/mod/wiki/wiki.module';
 | 
			
		||||
// @todo import { ADDON_MOD_WORKSHOP_SERVICES } from '@addons/mod/workshop/workshop.module';
 | 
			
		||||
@ -302,7 +302,7 @@ export class CoreCompileProvider {
 | 
			
		||||
            ...ADDON_MOD_QUIZ_SERVICES,
 | 
			
		||||
            ...ADDON_MOD_RESOURCE_SERVICES,
 | 
			
		||||
            // @todo ...ADDON_MOD_SCORM_SERVICES,
 | 
			
		||||
            // @todo ...ADDON_MOD_SURVEY_SERVICES,
 | 
			
		||||
            ...ADDON_MOD_SURVEY_SERVICES,
 | 
			
		||||
            ...ADDON_MOD_URL_SERVICES,
 | 
			
		||||
            // @todo ...ADDON_MOD_WIKI_SERVICES,
 | 
			
		||||
            // @todo ...ADDON_MOD_WORKSHOP_SERVICES,
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreSyncBaseProvider } from '@classes/base-sync';
 | 
			
		||||
import { CoreCourseAnyModuleData } from '../services/course';
 | 
			
		||||
import { CoreCourse, CoreCourseAnyModuleData } from '../services/course';
 | 
			
		||||
import { CoreCourseModulePrefetchDelegate } from '../services/module-prefetch-delegate';
 | 
			
		||||
import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler';
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,8 @@ import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler';
 | 
			
		||||
 */
 | 
			
		||||
export class CoreCourseActivitySyncBaseProvider<T = void> extends CoreSyncBaseProvider<T> {
 | 
			
		||||
 | 
			
		||||
    protected componentTranslatableString = 'activity';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Conveniece function to prefetch data after an update.
 | 
			
		||||
     *
 | 
			
		||||
@ -54,4 +56,15 @@ export class CoreCourseActivitySyncBaseProvider<T = void> extends CoreSyncBasePr
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected get componentTranslate(): string {
 | 
			
		||||
        if (!this.componentTranslateInternal) {
 | 
			
		||||
            this.componentTranslateInternal = CoreCourse.translateModuleName(this.componentTranslatableString);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return this.componentTranslateInternal;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ import { ModalOptions } from '@ionic/core';
 | 
			
		||||
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreLang } from '@services/lang';
 | 
			
		||||
import { CoreError } from '@classes/errors/error';
 | 
			
		||||
import { CoreAnyError, CoreError } from '@classes/errors/error';
 | 
			
		||||
import { makeSingleton, ModalController, Translate } from '@singletons';
 | 
			
		||||
import { CoreWSExternalFile } from '@services/ws';
 | 
			
		||||
import { Locutus } from '@singletons/locutus';
 | 
			
		||||
@ -533,7 +533,7 @@ export class CoreTextUtilsProvider {
 | 
			
		||||
     * @param error Error.
 | 
			
		||||
     * @return Error message, undefined if not found.
 | 
			
		||||
     */
 | 
			
		||||
    getErrorMessageFromError(error?: string | CoreError | CoreTextErrorObject | null): string | undefined {
 | 
			
		||||
    getErrorMessageFromError(error?: CoreAnyError): string | undefined {
 | 
			
		||||
        if (typeof error == 'string') {
 | 
			
		||||
            return error;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user