forked from CIT/Vmeda.Online
		
	MOBILE-2339 feedback: Implement Index page
This commit is contained in:
		
							parent
							
								
									0da7b03e3d
								
							
						
					
					
						commit
						c586db40dd
					
				@ -65,6 +65,7 @@
 | 
				
			|||||||
    "@types/cordova-plugin-network-information": "0.0.3",
 | 
					    "@types/cordova-plugin-network-information": "0.0.3",
 | 
				
			||||||
    "@types/node": "^8.0.47",
 | 
					    "@types/node": "^8.0.47",
 | 
				
			||||||
    "@types/promise.prototype.finally": "^2.0.2",
 | 
					    "@types/promise.prototype.finally": "^2.0.2",
 | 
				
			||||||
 | 
					    "chart.js": "^2.7.2",
 | 
				
			||||||
    "electron-builder-squirrel-windows": "^19.3.0",
 | 
					    "electron-builder-squirrel-windows": "^19.3.0",
 | 
				
			||||||
    "electron-windows-notifications": "^1.1.13",
 | 
					    "electron-windows-notifications": "^1.1.13",
 | 
				
			||||||
    "ionic-angular": "^3.9.2",
 | 
					    "ionic-angular": "^3.9.2",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										45
									
								
								src/addon/mod/feedback/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/addon/mod/feedback/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 { CommonModule } from '@angular/common';
 | 
				
			||||||
 | 
					import { IonicModule } from 'ionic-angular';
 | 
				
			||||||
 | 
					import { TranslateModule } from '@ngx-translate/core';
 | 
				
			||||||
 | 
					import { CoreComponentsModule } from '@components/components.module';
 | 
				
			||||||
 | 
					import { CoreDirectivesModule } from '@directives/directives.module';
 | 
				
			||||||
 | 
					import { CoreCourseComponentsModule } from '@core/course/components/components.module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackIndexComponent } from './index/index';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					    declarations: [
 | 
				
			||||||
 | 
					        AddonModFeedbackIndexComponent
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    imports: [
 | 
				
			||||||
 | 
					        CommonModule,
 | 
				
			||||||
 | 
					        IonicModule,
 | 
				
			||||||
 | 
					        TranslateModule.forChild(),
 | 
				
			||||||
 | 
					        CoreComponentsModule,
 | 
				
			||||||
 | 
					        CoreDirectivesModule,
 | 
				
			||||||
 | 
					        CoreCourseComponentsModule
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    providers: [
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    exports: [
 | 
				
			||||||
 | 
					        AddonModFeedbackIndexComponent
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    entryComponents: [
 | 
				
			||||||
 | 
					        AddonModFeedbackIndexComponent
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackComponentsModule {}
 | 
				
			||||||
							
								
								
									
										174
									
								
								src/addon/mod/feedback/components/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								src/addon/mod/feedback/components/index/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,174 @@
 | 
				
			|||||||
 | 
					<!-- Buttons to add to the header. -->
 | 
				
			||||||
 | 
					<core-navbar-buttons end>
 | 
				
			||||||
 | 
					    <core-context-menu>
 | 
				
			||||||
 | 
					        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl" [iconAction]="'open'"></core-context-menu-item>
 | 
				
			||||||
 | 
					        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'"></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()" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
 | 
				
			||||||
 | 
					        <core-context-menu-item *ngIf="size" [priority]="400" [content]="size" [iconDescription]="'cube'" (action)="removeFiles()" [iconAction]="'trash'"></core-context-menu-item>
 | 
				
			||||||
 | 
					    </core-context-menu>
 | 
				
			||||||
 | 
					</core-navbar-buttons>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Content. -->
 | 
				
			||||||
 | 
					<core-loading [hideUntil]="loaded" class="core-loading-center">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <core-course-module-description [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ng-container *ngIf="showTabs">
 | 
				
			||||||
 | 
					        <core-tabs [hideUntil]="tabsReady">
 | 
				
			||||||
 | 
					            <core-tab [title]="'addon.mod_feedback.overview' | translate" (ionSelect)="tabChanged('overview')">
 | 
				
			||||||
 | 
					                <ng-template>
 | 
				
			||||||
 | 
					                    <ng-container *ngTemplateOutlet="tabOverview"></ng-container>
 | 
				
			||||||
 | 
					                </ng-template>
 | 
				
			||||||
 | 
					            </core-tab>
 | 
				
			||||||
 | 
					            <core-tab [show]="access.canviewreports" [title]="'addon.mod_feedback.analysis' | translate" (ionSelect)="tabChanged('analysis')">
 | 
				
			||||||
 | 
					                <ng-template>
 | 
				
			||||||
 | 
					                    <ng-container *ngTemplateOutlet="tabAnalysis"></ng-container>
 | 
				
			||||||
 | 
					                </ng-template>
 | 
				
			||||||
 | 
					            </core-tab>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <core-tab [show]="!access.canviewreports" [title]="'addon.mod_feedback.completed_feedbacks' | translate" (ionSelect)="tabChanged('analysis')">
 | 
				
			||||||
 | 
					                <ng-template>
 | 
				
			||||||
 | 
					                    <ng-container *ngTemplateOutlet="tabAnalysis"></ng-container>
 | 
				
			||||||
 | 
					                </ng-template>
 | 
				
			||||||
 | 
					            </core-tab>
 | 
				
			||||||
 | 
					        </core-tabs>
 | 
				
			||||||
 | 
					    </ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ng-container *ngIf="!showTabs">
 | 
				
			||||||
 | 
					        <ng-container *ngTemplateOutlet="tabOverview"></ng-container>
 | 
				
			||||||
 | 
					    </ng-container>
 | 
				
			||||||
 | 
					</core-loading>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ng-template #basicInfo>
 | 
				
			||||||
 | 
					    <ion-list *ngIf="(access.canedititems || access.canviewreports) && !access.isempty">
 | 
				
			||||||
 | 
					        <ion-item text-wrap *ngIf="access.canedititems && (groupInfo.separateGroups || groupInfo.visibleGroups)">
 | 
				
			||||||
 | 
					            <ion-label id="addon-feedback-groupslabel" *ngIf="groupInfo.separateGroups">{{ 'core.groupsseparate' | translate }}</ion-label>
 | 
				
			||||||
 | 
					            <ion-label id="addon-feedback-groupslabel" *ngIf="groupInfo.visibleGroups">{{ 'core.groupsvisible' | translate }}</ion-label>
 | 
				
			||||||
 | 
					            <ion-select [(ngModel)]="group" (ionChange)="setGroup(group)" aria-labelledby="addon-feedback-groupslabel">
 | 
				
			||||||
 | 
					                <ion-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">{{groupOpt.name}}</ion-option>
 | 
				
			||||||
 | 
					            </ion-select>
 | 
				
			||||||
 | 
					        </ion-item>
 | 
				
			||||||
 | 
					        <ion-item text-wrap *ngIf="access.canviewreports || access.canedititems" (click)="access.canviewreports && openFeature('Respondents')" [attr.detail-push]="access.canviewreports ? true : null">
 | 
				
			||||||
 | 
					            <h2>{{ 'addon.mod_feedback.completed_feedbacks' | translate }}</h2>
 | 
				
			||||||
 | 
					            <ion-badge item-end>{{feedback.completedCount}}</ion-badge>
 | 
				
			||||||
 | 
					        </ion-item>
 | 
				
			||||||
 | 
					        <ion-item text-wrap *ngIf="!access.isanonymous && access.canviewreports" (click)="openFeature('NonRespondents')" detail-push>
 | 
				
			||||||
 | 
					            <h2>{{ 'addon.mod_feedback.show_nonrespondents' | translate }}</h2>
 | 
				
			||||||
 | 
					        </ion-item>
 | 
				
			||||||
 | 
					        <ion-item text-wrap *ngIf="access.canedititems">
 | 
				
			||||||
 | 
					            <h2>{{ 'addon.mod_feedback.questions' | translate }}</h2>
 | 
				
			||||||
 | 
					            <ion-badge item-end>{{feedback.itemsCount}}</ion-badge>
 | 
				
			||||||
 | 
					        </ion-item>
 | 
				
			||||||
 | 
					    </ion-list>
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Template to render the overview. -->
 | 
				
			||||||
 | 
					<ng-template #tabOverview>
 | 
				
			||||||
 | 
					    <core-loading [hideUntil]="tabsLoaded.overview">
 | 
				
			||||||
 | 
					        <ng-container *ngTemplateOutlet="basicInfo"></ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <!-- Feedback done in offline but not synchronized -->
 | 
				
			||||||
 | 
					        <div class="core-warning-card" icon-start *ngIf="hasOffline">
 | 
				
			||||||
 | 
					            <ion-icon name="warning"></ion-icon>
 | 
				
			||||||
 | 
					            {{ 'core.hasdatatosync' | translate: {$a: moduleName} }}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="core-info-card" icon-start *ngIf="access.cancomplete && !access.isopen">
 | 
				
			||||||
 | 
					            <ion-icon name="information-circle"></ion-icon>
 | 
				
			||||||
 | 
					            {{ 'addon.mod_feedback.feedback_is_not_open' | translate }}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="core-success-card" *ngIf="access.cancomplete && access.isopen && !access.cansubmit">
 | 
				
			||||||
 | 
					            <ion-icon name="checkmark"></ion-icon>
 | 
				
			||||||
 | 
					            {{ 'addon.mod_feedback.this_feedback_is_already_submitted' | translate }}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ion-list *ngIf="access.canedititems || access.canviewreports || !access.isempty">
 | 
				
			||||||
 | 
					            <ion-item text-wrap *ngIf="access.canedititems && overview.timeopen">
 | 
				
			||||||
 | 
					                <h2>{{ 'addon.mod_feedback.feedbackopen' | translate }}</h2>
 | 
				
			||||||
 | 
					                <p>{{overview.openTimeReadable}}</p>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					            <ion-item text-wrap *ngIf="access.canedititems && overview.timeclose">
 | 
				
			||||||
 | 
					                <h2>{{ 'addon.mod_feedback.feedbackclose' | translate }}</h2>
 | 
				
			||||||
 | 
					                <p>{{overview.closeTimeReadable}}</p>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					            <ion-item text-wrap *ngIf="access.canedititems && feedback.page_after_submit">
 | 
				
			||||||
 | 
					                <h2>{{ 'addon.mod_feedback.page_after_submit' | translate }}</h2>
 | 
				
			||||||
 | 
					                <core-format-text  [component]="component" [componentId]="componentId" [text]=" feedback.page_after_submit"></core-format-text>
 | 
				
			||||||
 | 
					            </ion-item>
 | 
				
			||||||
 | 
					            <ng-container *ngIf="!access.isempty">
 | 
				
			||||||
 | 
					                <ion-item text-wrap>
 | 
				
			||||||
 | 
					                    <h2>{{ 'addon.mod_feedback.mode' | translate }}</h2>
 | 
				
			||||||
 | 
					                    <p *ngIf="access.isanonymous">{{ 'addon.mod_feedback.anonymous' | translate }}</p>
 | 
				
			||||||
 | 
					                    <p *ngIf="!access.isanonymous">{{ 'addon.mod_feedback.non_anonymous' | translate }}</p>
 | 
				
			||||||
 | 
					                </ion-item>
 | 
				
			||||||
 | 
					                <ion-grid>
 | 
				
			||||||
 | 
					                    <ion-row align-items-center>
 | 
				
			||||||
 | 
					                        <ion-col>
 | 
				
			||||||
 | 
					                            <button ion-button block outline icon-start (click)="gotoAnswerQuestions(true)">
 | 
				
			||||||
 | 
					                                <ion-icon name="search"></ion-icon>
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.preview' | translate }}
 | 
				
			||||||
 | 
					                            </button>
 | 
				
			||||||
 | 
					                        </ion-col>
 | 
				
			||||||
 | 
					                        <ion-col *ngIf="access.cancomplete && access.cansubmit">
 | 
				
			||||||
 | 
					                            <button ion-button block icon-end *ngIf="!goPage" (click)="gotoAnswerQuestions()">
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.complete_the_form' | translate }}
 | 
				
			||||||
 | 
					                                <ion-icon name="arrow-forward"></ion-icon>
 | 
				
			||||||
 | 
					                            </button>
 | 
				
			||||||
 | 
					                            <button ion-button block icon-end *ngIf="goPage" (click)="gotoAnswerQuestions()">
 | 
				
			||||||
 | 
					                                {{ 'addon.mod_feedback.continue_the_form' | translate }}
 | 
				
			||||||
 | 
					                                <ion-icon name="arrow-forward"></ion-icon>
 | 
				
			||||||
 | 
					                            </button>
 | 
				
			||||||
 | 
					                        </ion-col>
 | 
				
			||||||
 | 
					                    </ion-row>
 | 
				
			||||||
 | 
					                </ion-grid>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            </ng-container>
 | 
				
			||||||
 | 
					        </ion-list>
 | 
				
			||||||
 | 
					    </core-loading>
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Template to render the analysis. -->
 | 
				
			||||||
 | 
					<ng-template #tabAnalysis>
 | 
				
			||||||
 | 
					    <core-loading [hideUntil]="tabsLoaded.analysis">
 | 
				
			||||||
 | 
					        <ng-container *ngTemplateOutlet="basicInfo"></ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ng-container *ngIf="access.canedititems || !access.isempty">
 | 
				
			||||||
 | 
					            <div class="core-info-card" icon-start *ngIf="warning">
 | 
				
			||||||
 | 
					                <ion-icon name="information-circle"></ion-icon>
 | 
				
			||||||
 | 
					                {{ warning }}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <ion-list *ngIf="items && items.length">
 | 
				
			||||||
 | 
					                <ion-item text-wrap *ngFor="let item of items" class="core-analysis">
 | 
				
			||||||
 | 
					                    <h2>{{item.number}}. {{ item.name }}</h2>
 | 
				
			||||||
 | 
					                    <p>{{ item.label }}</p>
 | 
				
			||||||
 | 
					                    <ng-container [ngSwitch]="item.template">
 | 
				
			||||||
 | 
					                        <ng-container *ngSwitchCase="'numeric'">
 | 
				
			||||||
 | 
					                            <ul>
 | 
				
			||||||
 | 
					                                <li *ngFor="let data of item.data">{{ data }}</li>
 | 
				
			||||||
 | 
					                            </ul>
 | 
				
			||||||
 | 
					                            <p>{{ 'addon.mod_feedback.average' | translate }}: {{item.average | number : '1.2-2'}}</p>
 | 
				
			||||||
 | 
					                        </ng-container>
 | 
				
			||||||
 | 
					                        <ng-container *ngSwitchCase="'list'">
 | 
				
			||||||
 | 
					                            <ul>
 | 
				
			||||||
 | 
					                                <ng-container *ngFor="let data of item.data">
 | 
				
			||||||
 | 
					                                    <li *ngIf="data">
 | 
				
			||||||
 | 
					                                        <core-format-text [text]="data"></core-format-text>
 | 
				
			||||||
 | 
					                                    </li>
 | 
				
			||||||
 | 
					                                </ng-container>
 | 
				
			||||||
 | 
					                            </ul>
 | 
				
			||||||
 | 
					                        </ng-container>
 | 
				
			||||||
 | 
					                        <ng-container *ngSwitchCase="'chart'">
 | 
				
			||||||
 | 
					                            <canvas core-chart [type]="item.chartType" [data]="item.chartData" [labels]="item.labels" height="300"></canvas>
 | 
				
			||||||
 | 
					                            <p *ngIf="item.average">{{ 'addon.mod_feedback.average' | translate }}: {{item.average | number : '1.2-2'}}</p>
 | 
				
			||||||
 | 
					                        </ng-container>
 | 
				
			||||||
 | 
					                    </ng-container>
 | 
				
			||||||
 | 
					                </ion-item>
 | 
				
			||||||
 | 
					            </ion-list>
 | 
				
			||||||
 | 
					        </ng-container>
 | 
				
			||||||
 | 
					    </core-loading>
 | 
				
			||||||
 | 
					</ng-template>
 | 
				
			||||||
							
								
								
									
										435
									
								
								src/addon/mod/feedback/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										435
									
								
								src/addon/mod/feedback/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,435 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					// you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					// You may obtain a copy of the License at
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					// distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					// See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Component, Input, Optional, Injector } from '@angular/core';
 | 
				
			||||||
 | 
					import { Content, NavController } from 'ionic-angular';
 | 
				
			||||||
 | 
					import { CoreGroupInfo, CoreGroupsProvider } from '@providers/groups';
 | 
				
			||||||
 | 
					import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
 | 
				
			||||||
 | 
					import { AddonModFeedbackProvider } from '../../providers/feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackHelperProvider } from '../../providers/helper';
 | 
				
			||||||
 | 
					import { AddonModFeedbackOfflineProvider } from '../../providers/offline';
 | 
				
			||||||
 | 
					import { AddonModFeedbackSyncProvider } from '../../providers/sync';
 | 
				
			||||||
 | 
					import * as moment from 'moment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Component that displays a feedback index page.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					    selector: 'addon-mod-feedback-index',
 | 
				
			||||||
 | 
					    templateUrl: 'index.html',
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivityComponent {
 | 
				
			||||||
 | 
					    @Input() tab = 'overview';
 | 
				
			||||||
 | 
					    @Input() group = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    moduleName = 'feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    access = {
 | 
				
			||||||
 | 
					        canviewreports: false,
 | 
				
			||||||
 | 
					        canviewanalysis: false,
 | 
				
			||||||
 | 
					        isempty: true
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    feedback: any;
 | 
				
			||||||
 | 
					    goPage: number;
 | 
				
			||||||
 | 
					    groupInfo: CoreGroupInfo = {
 | 
				
			||||||
 | 
					        groups: [],
 | 
				
			||||||
 | 
					        separateGroups: false,
 | 
				
			||||||
 | 
					        visibleGroups: false
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    items: any[];
 | 
				
			||||||
 | 
					    overview = {
 | 
				
			||||||
 | 
					        timeopen: 0,
 | 
				
			||||||
 | 
					        openTimeReadable: '',
 | 
				
			||||||
 | 
					        timeclose: 0,
 | 
				
			||||||
 | 
					        closeTimeReadable: ''
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    warning = '';
 | 
				
			||||||
 | 
					    tabsLoaded = {
 | 
				
			||||||
 | 
					        overview: false,
 | 
				
			||||||
 | 
					        analysis: false
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    showTabs = false;
 | 
				
			||||||
 | 
					    tabsReady = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected submitObserver: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(injector: Injector, private feedbackProvider: AddonModFeedbackProvider, @Optional() private content: Content,
 | 
				
			||||||
 | 
					            private feedbackOffline: AddonModFeedbackOfflineProvider, private groupsProvider: CoreGroupsProvider,
 | 
				
			||||||
 | 
					            private feedbackSync: AddonModFeedbackSyncProvider, private navCtrl: NavController,
 | 
				
			||||||
 | 
					            private feedbackHelper: AddonModFeedbackHelperProvider) {
 | 
				
			||||||
 | 
					        super(injector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Listen for form submit events.
 | 
				
			||||||
 | 
					        this.submitObserver = this.eventsProvider.on(AddonModFeedbackProvider.FORM_SUBMITTED, (data) => {
 | 
				
			||||||
 | 
					            if (this.feedback && data.feedbackId == this.feedback.id) {
 | 
				
			||||||
 | 
					                // Go to review attempt if an attempt in this quiz was finished and synced.
 | 
				
			||||||
 | 
					                this.tabsLoaded['analysis'] = false;
 | 
				
			||||||
 | 
					                this.tabsLoaded['overview'] = false;
 | 
				
			||||||
 | 
					                this.loaded = false;
 | 
				
			||||||
 | 
					                if (data.tab != this.tab) {
 | 
				
			||||||
 | 
					                    this.tabChanged(data.tab);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    this.loadContent(true);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }, this.siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Component being initialized.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ngOnInit(): void {
 | 
				
			||||||
 | 
					        super.ngOnInit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.loadContent(false, true).then(() => {
 | 
				
			||||||
 | 
					            this.feedbackProvider.logView(this.feedback.id);
 | 
				
			||||||
 | 
					        }).finally(() => {
 | 
				
			||||||
 | 
					            this.tabsReady = true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Perform the invalidate content function.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return {Promise<any>} Resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected invalidateContent(): Promise<any> {
 | 
				
			||||||
 | 
					        const promises = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        promises.push(this.feedbackProvider.invalidateFeedbackData(this.courseId));
 | 
				
			||||||
 | 
					        if (this.feedback) {
 | 
				
			||||||
 | 
					            promises.push(this.feedbackProvider.invalidateFeedbackAccessInformationData(this.feedback.id));
 | 
				
			||||||
 | 
					            promises.push(this.feedbackProvider.invalidateAnalysisData(this.feedback.id));
 | 
				
			||||||
 | 
					            promises.push(this.groupsProvider.invalidateActivityAllowedGroups(this.feedback.coursemodule));
 | 
				
			||||||
 | 
					            promises.push(this.groupsProvider.invalidateActivityGroupMode(this.feedback.coursemodule));
 | 
				
			||||||
 | 
					            promises.push(this.feedbackProvider.invalidateResumePageData(this.feedback.id));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.tabsLoaded['analysis'] = false;
 | 
				
			||||||
 | 
					        this.tabsLoaded['overview'] = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Promise.all(promises);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Compares sync event data with current data to check if refresh content is needed.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {any} syncEventData Data receiven on sync observer.
 | 
				
			||||||
 | 
					     * @return {boolean}          True if refresh is needed, false otherwise.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected isRefreshSyncNeeded(syncEventData: any): boolean {
 | 
				
			||||||
 | 
					        if (this.feedback && syncEventData.feedbackId == this.feedback.id) {
 | 
				
			||||||
 | 
					            // Refresh the data.
 | 
				
			||||||
 | 
					            this.content.scrollToTop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Download feedback contents.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {boolean}      [refresh=false]    If it's refreshing content.
 | 
				
			||||||
 | 
					     * @param  {boolean}      [sync=false]       If the refresh is needs syncing.
 | 
				
			||||||
 | 
					     * @param  {boolean}      [showErrors=false] If show errors to the user of hide them.
 | 
				
			||||||
 | 
					     * @return {Promise<any>} Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<any> {
 | 
				
			||||||
 | 
					        return this.feedbackProvider.getFeedback(this.courseId, this.module.id).then((feedback) => {
 | 
				
			||||||
 | 
					            this.feedback = feedback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.description = feedback.intro || feedback.description;
 | 
				
			||||||
 | 
					            this.dataRetrieved.emit(feedback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (sync) {
 | 
				
			||||||
 | 
					                // Try to synchronize the feedback.
 | 
				
			||||||
 | 
					                return this.syncActivity(showErrors);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }).then(() => {
 | 
				
			||||||
 | 
					            // Check if there are answers stored in offline.
 | 
				
			||||||
 | 
					            return this.feedbackProvider.getFeedbackAccessInformation(this.feedback.id);
 | 
				
			||||||
 | 
					        }).then((accessData) => {
 | 
				
			||||||
 | 
					            this.access = accessData;
 | 
				
			||||||
 | 
					            this.showTabs = (accessData.canviewreports || accessData.canviewanalysis) && !accessData.isempty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.tab == 'analysis') {
 | 
				
			||||||
 | 
					                return this.fetchFeedbackAnalysisData(this.access);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return this.fetchFeedbackOverviewData(this.access);
 | 
				
			||||||
 | 
					        }).then(() => {
 | 
				
			||||||
 | 
					            // All data obtained, now fill the context menu.
 | 
				
			||||||
 | 
					            this.fillContextMenu(refresh);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check if there are responses stored in offline.
 | 
				
			||||||
 | 
					            return this.feedbackOffline.hasFeedbackOfflineData(this.feedback.id);
 | 
				
			||||||
 | 
					        }).then((hasOffline) => {
 | 
				
			||||||
 | 
					            this.hasOffline = hasOffline;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Convenience function to get feedback overview data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {any} accessData Retrieved access data.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}  Resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected fetchFeedbackOverviewData(accessData: any): Promise<any> {
 | 
				
			||||||
 | 
					        const promises = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (accessData.cancomplete && accessData.cansubmit && accessData.isopen) {
 | 
				
			||||||
 | 
					            promises.push(this.feedbackProvider.getResumePage(this.feedback.id).then((goPage) => {
 | 
				
			||||||
 | 
					                this.goPage = goPage > 0 ? goPage : false;
 | 
				
			||||||
 | 
					            }));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (accessData.canedititems) {
 | 
				
			||||||
 | 
					            this.overview.timeopen = parseInt(this.feedback.timeopen) * 1000 || 0;
 | 
				
			||||||
 | 
					            this.overview.openTimeReadable = this.overview.timeopen ?
 | 
				
			||||||
 | 
					                moment(this.overview.timeopen).format('LLL') : '';
 | 
				
			||||||
 | 
					            this.overview.timeclose = parseInt(this.feedback.timeclose) * 1000 || 0;
 | 
				
			||||||
 | 
					            this.overview.closeTimeReadable = this.overview.timeclose ?
 | 
				
			||||||
 | 
					                moment(this.overview.timeclose).format('LLL') : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get groups (only for teachers).
 | 
				
			||||||
 | 
					            promises.push(this.fetchGroupInfo(this.feedback.coursemodule));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Promise.all(promises).finally(() => {
 | 
				
			||||||
 | 
					            this.tabsLoaded['overview'] = true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Convenience function to get feedback analysis data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {any} accessData Retrieved access data.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}  Resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected fetchFeedbackAnalysisData(accessData: any): Promise<any> {
 | 
				
			||||||
 | 
					        let promise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (accessData.canviewanalysis) {
 | 
				
			||||||
 | 
					            // Get groups (only for teachers).
 | 
				
			||||||
 | 
					            promise = this.fetchGroupInfo(this.feedback.coursemodule);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            this.tabChanged('overview');
 | 
				
			||||||
 | 
					            promise = Promise.resolve();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return promise.finally(() => {
 | 
				
			||||||
 | 
					            this.tabsLoaded['analysis'] = true;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fetch Group info data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number}       cmId Course module ID.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}      Resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected fetchGroupInfo(cmId: number): Promise<any> {
 | 
				
			||||||
 | 
					        return this.groupsProvider.getActivityGroupInfo(cmId).then((groupInfo) => {
 | 
				
			||||||
 | 
					            this.groupInfo = groupInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return this.setGroup(this.group);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Parse the analysis info to show the info correctly formatted.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {any} item Item to parse.
 | 
				
			||||||
 | 
					     * @return {any}      Parsed item.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected parseAnalysisInfo(item: any): any {
 | 
				
			||||||
 | 
					        switch (item.typ) {
 | 
				
			||||||
 | 
					            case 'numeric':
 | 
				
			||||||
 | 
					                item.average = item.data.reduce((prev, current) => {
 | 
				
			||||||
 | 
					                    return prev + parseInt(current, 10);
 | 
				
			||||||
 | 
					                }, 0) / item.data.length;
 | 
				
			||||||
 | 
					                item.template = 'numeric';
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case 'info':
 | 
				
			||||||
 | 
					                item.data = item.data.map((dataItem) => {
 | 
				
			||||||
 | 
					                    dataItem = this.textUtils.parseJSON(dataItem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return typeof dataItem.show != 'undefined' ? dataItem.show : false;
 | 
				
			||||||
 | 
					                }).filter((dataItem) => {
 | 
				
			||||||
 | 
					                    // Filter false entries.
 | 
				
			||||||
 | 
					                    return dataItem;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case 'textfield':
 | 
				
			||||||
 | 
					            case 'textarea':
 | 
				
			||||||
 | 
					                item.template = 'list';
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            case 'multichoicerated':
 | 
				
			||||||
 | 
					            case 'multichoice':
 | 
				
			||||||
 | 
					                item.data = item.data.map((dataItem) => {
 | 
				
			||||||
 | 
					                    dataItem = this.textUtils.parseJSON(dataItem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return typeof dataItem.answertext != 'undefined' ? dataItem : false;
 | 
				
			||||||
 | 
					                }).filter((dataItem) => {
 | 
				
			||||||
 | 
					                    // Filter false entries.
 | 
				
			||||||
 | 
					                    return dataItem;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Format labels.
 | 
				
			||||||
 | 
					                item.labels = item.data.map((dataItem) => {
 | 
				
			||||||
 | 
					                    dataItem.quotient = (dataItem.quotient * 100).toFixed(2);
 | 
				
			||||||
 | 
					                    let label = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (typeof dataItem.value != 'undefined') {
 | 
				
			||||||
 | 
					                        label = '(' + dataItem.value + ') ';
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    label += dataItem.answertext;
 | 
				
			||||||
 | 
					                    label += dataItem.quotient > 0 ? ' (' + dataItem.quotient + '%)' : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return label;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                item.chartData = item.data.map((dataItem) => {
 | 
				
			||||||
 | 
					                    return dataItem.answercount;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (item.typ == 'multichoicerated') {
 | 
				
			||||||
 | 
					                    item.average = item.data.reduce((prev, current) => {
 | 
				
			||||||
 | 
					                        return prev + parseFloat(current.avg);
 | 
				
			||||||
 | 
					                    }, 0.0);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const subtype = item.presentation.charAt(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const single = subtype != 'c';
 | 
				
			||||||
 | 
					                item.chartType = single ? 'doughnut' : 'bar';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                item.template = 'chart';
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					               break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return item;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Function to go to the questions form.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {boolean} preview Preview or edit the form.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    gotoAnswerQuestions(preview: boolean): void {
 | 
				
			||||||
 | 
					        const stateParams = {
 | 
				
			||||||
 | 
					            module: this.module,
 | 
				
			||||||
 | 
					            moduleid: this.module.id,
 | 
				
			||||||
 | 
					            courseid: this.courseId,
 | 
				
			||||||
 | 
					            preview: preview
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        this.navCtrl.push('AddonModFeedbackFormPage', stateParams);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Function to link implemented features.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {string} feature Feature to navigate.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    openFeature(feature: string): void {
 | 
				
			||||||
 | 
					        this.feedbackHelper.openFeature(feature, this.navCtrl, this.module, this.courseId, this.group);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Tab changed, fetch content again.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {string} tabName New tab name.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    tabChanged(tabName: string): void {
 | 
				
			||||||
 | 
					        this.tab = tabName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!this.tabsLoaded[this.tab]) {
 | 
				
			||||||
 | 
					            this.loadContent(false, false, true);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Set group to see the analysis.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number}       groupId Group ID.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}         Resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    setGroup(groupId: number): Promise<any> {
 | 
				
			||||||
 | 
					        this.group = groupId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.feedbackProvider.getAnalysis(this.feedback.id, groupId).then((analysis) => {
 | 
				
			||||||
 | 
					            this.feedback.completedCount = analysis.completedcount;
 | 
				
			||||||
 | 
					            this.feedback.itemsCount = analysis.itemscount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this.tab == 'analysis') {
 | 
				
			||||||
 | 
					                let num = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                this.items = analysis.itemsdata.map((item) => {
 | 
				
			||||||
 | 
					                    // Move data inside item.
 | 
				
			||||||
 | 
					                    item.item.data = item.data;
 | 
				
			||||||
 | 
					                    item = item.item;
 | 
				
			||||||
 | 
					                    item.number = num++;
 | 
				
			||||||
 | 
					                    if (item.data && item.data.length) {
 | 
				
			||||||
 | 
					                        return this.parseAnalysisInfo(item);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }).filter((item) => {
 | 
				
			||||||
 | 
					                    return item;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                this.warning = '';
 | 
				
			||||||
 | 
					                if (analysis.warnings.length) {
 | 
				
			||||||
 | 
					                    this.warning = analysis.warnings.find((warning) => {
 | 
				
			||||||
 | 
					                        return warning.warningcode == 'insufficientresponsesforthisgroup';
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Performs the sync of the activity.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return {Promise<any>} Promise resolved when done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected sync(): Promise<any> {
 | 
				
			||||||
 | 
					        return this.feedbackSync.syncFeedback(this.feedback.id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Checks if sync has succeed from result sync data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {any}     result Data returned on the sync function.
 | 
				
			||||||
 | 
					     * @return {boolean}        If suceed or not.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected hasSyncSucceed(result: any): boolean {
 | 
				
			||||||
 | 
					        return result.updated;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Component being destroyed.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ngOnDestroy(): void {
 | 
				
			||||||
 | 
					        super.ngOnDestroy();
 | 
				
			||||||
 | 
					        this.submitObserver && this.submitObserver.off();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										57
									
								
								src/addon/mod/feedback/feedback.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/addon/mod/feedback/feedback.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 { CoreCronDelegate } from '@providers/cron';
 | 
				
			||||||
 | 
					import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
 | 
				
			||||||
 | 
					import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate';
 | 
				
			||||||
 | 
					import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
 | 
				
			||||||
 | 
					import { AddonModFeedbackComponentsModule } from './components/components.module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackModuleHandler } from './providers/module-handler';
 | 
				
			||||||
 | 
					import { AddonModFeedbackProvider } from './providers/feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackLinkHandler } from './providers/link-handler';
 | 
				
			||||||
 | 
					import { AddonModFeedbackHelperProvider } from './providers/helper';
 | 
				
			||||||
 | 
					import { AddonModFeedbackPrefetchHandler } from './providers/prefetch-handler';
 | 
				
			||||||
 | 
					import { AddonModFeedbackSyncProvider } from './providers/sync';
 | 
				
			||||||
 | 
					import { AddonModFeedbackSyncCronHandler } from './providers/sync-cron-handler';
 | 
				
			||||||
 | 
					import { AddonModFeedbackOfflineProvider } from './providers/offline';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					    declarations: [
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    imports: [
 | 
				
			||||||
 | 
					        AddonModFeedbackComponentsModule
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    providers: [
 | 
				
			||||||
 | 
					        AddonModFeedbackProvider,
 | 
				
			||||||
 | 
					        AddonModFeedbackModuleHandler,
 | 
				
			||||||
 | 
					        AddonModFeedbackPrefetchHandler,
 | 
				
			||||||
 | 
					        AddonModFeedbackHelperProvider,
 | 
				
			||||||
 | 
					        AddonModFeedbackLinkHandler,
 | 
				
			||||||
 | 
					        AddonModFeedbackSyncCronHandler,
 | 
				
			||||||
 | 
					        AddonModFeedbackSyncProvider,
 | 
				
			||||||
 | 
					        AddonModFeedbackOfflineProvider
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackModule {
 | 
				
			||||||
 | 
					    constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModFeedbackModuleHandler,
 | 
				
			||||||
 | 
					            prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModFeedbackPrefetchHandler,
 | 
				
			||||||
 | 
					            contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModFeedbackLinkHandler,
 | 
				
			||||||
 | 
					            cronDelegate: CoreCronDelegate, syncHandler: AddonModFeedbackSyncCronHandler) {
 | 
				
			||||||
 | 
					        moduleDelegate.registerHandler(moduleHandler);
 | 
				
			||||||
 | 
					        prefetchDelegate.registerHandler(prefetchHandler);
 | 
				
			||||||
 | 
					        contentLinksDelegate.registerHandler(linkHandler);
 | 
				
			||||||
 | 
					        cronDelegate.register(syncHandler);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/addon/mod/feedback/lang/en.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/addon/mod/feedback/lang/en.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "analysis": "Analysis",
 | 
				
			||||||
 | 
					    "anonymous": "Anonymous",
 | 
				
			||||||
 | 
					    "average": "Average",
 | 
				
			||||||
 | 
					    "completed_feedbacks": "Submitted answers",
 | 
				
			||||||
 | 
					    "complete_the_form": "Answer the questions...",
 | 
				
			||||||
 | 
					    "continue_the_form": "Continue the form",
 | 
				
			||||||
 | 
					    "feedbackclose": "Allow answers to",
 | 
				
			||||||
 | 
					    "feedbackopen": "Allow answers from",
 | 
				
			||||||
 | 
					    "mode": "Mode",
 | 
				
			||||||
 | 
					    "non_anonymous": "User's name will be logged and shown with answers",
 | 
				
			||||||
 | 
					    "overview": "Overview",
 | 
				
			||||||
 | 
					    "page_after_submit": "Completion message",
 | 
				
			||||||
 | 
					    "preview": "Preview",
 | 
				
			||||||
 | 
					    "questions": "Questions",
 | 
				
			||||||
 | 
					    "show_nonrespondents": "Show non-respondents",
 | 
				
			||||||
 | 
					    "this_feedback_is_already_submitted": "You've already completed this activity."
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								src/addon/mod/feedback/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/addon/mod/feedback/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					<ion-header>
 | 
				
			||||||
 | 
					    <ion-navbar>
 | 
				
			||||||
 | 
					        <ion-title><core-format-text [text]="title"></core-format-text></ion-title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <ion-buttons end>
 | 
				
			||||||
 | 
					            <!-- The buttons defined by the component will be added in here. -->
 | 
				
			||||||
 | 
					        </ion-buttons>
 | 
				
			||||||
 | 
					    </ion-navbar>
 | 
				
			||||||
 | 
					</ion-header>
 | 
				
			||||||
 | 
					<ion-content>
 | 
				
			||||||
 | 
					    <ion-refresher [enabled]="feedbackComponent.loaded" (ionRefresh)="feedbackComponent.doRefresh($event)">
 | 
				
			||||||
 | 
					        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
				
			||||||
 | 
					    </ion-refresher>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <addon-mod-feedback-index [module]="module" [courseId]="courseId" [group]="selectedGroup" [tab]="selectedTab" (dataRetrieved)="updateData($event)"></addon-mod-feedback-index>
 | 
				
			||||||
 | 
					</ion-content>
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/addon/mod/feedback/pages/index/index.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/addon/mod/feedback/pages/index/index.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 { IonicPageModule } from 'ionic-angular';
 | 
				
			||||||
 | 
					import { TranslateModule } from '@ngx-translate/core';
 | 
				
			||||||
 | 
					import { CoreDirectivesModule } from '@directives/directives.module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackComponentsModule } from '../../components/components.module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackIndexPage } from './index';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@NgModule({
 | 
				
			||||||
 | 
					    declarations: [
 | 
				
			||||||
 | 
					        AddonModFeedbackIndexPage,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    imports: [
 | 
				
			||||||
 | 
					        CoreDirectivesModule,
 | 
				
			||||||
 | 
					        AddonModFeedbackComponentsModule,
 | 
				
			||||||
 | 
					        IonicPageModule.forChild(AddonModFeedbackIndexPage),
 | 
				
			||||||
 | 
					        TranslateModule.forChild()
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackIndexPageModule {}
 | 
				
			||||||
							
								
								
									
										52
									
								
								src/addon/mod/feedback/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/addon/mod/feedback/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 { IonicPage, NavParams } from 'ionic-angular';
 | 
				
			||||||
 | 
					import { AddonModFeedbackIndexComponent } from '../../components/index/index';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Page that displays a feedback.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@IonicPage({ segment: 'addon-mod-feedback-index' })
 | 
				
			||||||
 | 
					@Component({
 | 
				
			||||||
 | 
					    selector: 'page-addon-mod-feedback-index',
 | 
				
			||||||
 | 
					    templateUrl: 'index.html',
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class AddonModFeedbackIndexPage {
 | 
				
			||||||
 | 
					    @ViewChild(AddonModFeedbackIndexComponent) feedbackComponent: AddonModFeedbackIndexComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    title: string;
 | 
				
			||||||
 | 
					    module: any;
 | 
				
			||||||
 | 
					    courseId: number;
 | 
				
			||||||
 | 
					    selectedTab: string;
 | 
				
			||||||
 | 
					    selectedGroup: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(navParams: NavParams) {
 | 
				
			||||||
 | 
					        this.module = navParams.get('module') || {};
 | 
				
			||||||
 | 
					        this.courseId = navParams.get('courseId');
 | 
				
			||||||
 | 
					        this.selectedGroup = navParams.get('group') || 0;
 | 
				
			||||||
 | 
					        this.selectedTab = navParams.get('tab') || 'overview';
 | 
				
			||||||
 | 
					        this.title = this.module.name;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Update some data based on the feedback instance.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {any} feedback Feedback instance.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    updateData(feedback: any): void {
 | 
				
			||||||
 | 
					        this.title = feedback.name || this.title;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										548
									
								
								src/addon/mod/feedback/providers/feedback.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										548
									
								
								src/addon/mod/feedback/providers/feedback.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,548 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 { CoreLoggerProvider } from '@providers/logger';
 | 
				
			||||||
 | 
					import { CoreSitesProvider } from '@providers/sites';
 | 
				
			||||||
 | 
					import { CoreUtilsProvider } from '@providers/utils/utils';
 | 
				
			||||||
 | 
					import { CoreFilepoolProvider } from '@providers/filepool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Service that provides some features for feedbacks.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class AddonModFeedbackProvider {
 | 
				
			||||||
 | 
					    static COMPONENT = 'mmaModFeedback';
 | 
				
			||||||
 | 
					    static FORM_SUBMITTED = 'addon_mod_feedback_form_submitted';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected ROOT_CACHE_KEY = this.ROOT_CACHE_KEY + '';
 | 
				
			||||||
 | 
					    protected logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
 | 
				
			||||||
 | 
					            private filepoolProvider: CoreFilepoolProvider) {
 | 
				
			||||||
 | 
					        this.logger = logger.getInstance('AddonModFeedbackProvider');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get analysis information for a given feedback.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}    feedbackId      Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {number}    [groupId]       Group ID.
 | 
				
			||||||
 | 
					     * @param   {string}    [siteId]        Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}                   Promise resolved when the feedback is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getAnalysis(feedbackId: number, groupId?: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            const params = {
 | 
				
			||||||
 | 
					                    feedbackid: feedbackId
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                preSets = {
 | 
				
			||||||
 | 
					                    cacheKey: this.getAnalysisDataCacheKey(feedbackId, groupId)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (groupId) {
 | 
				
			||||||
 | 
					                params['groupid'] = groupId;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return site.read('mod_feedback_get_analysis', params, preSets);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get cache key for feedback analysis data WS calls.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param {number} [groupId=0]  Group ID.
 | 
				
			||||||
 | 
					     * @return {string}         Cache key.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getAnalysisDataCacheKey(feedbackId: number, groupId: number = 0): string {
 | 
				
			||||||
 | 
					        return this.getAnalysisDataPrefixCacheKey(feedbackId) + groupId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get prefix cache key for feedback analysis data WS calls.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @return {string}         Cache key.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getAnalysisDataPrefixCacheKey(feedbackId: number): string {
 | 
				
			||||||
 | 
					        return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':analysis:';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get prefix cache key for feedback completion data WS calls.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @return {string}         Cache key.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getCompletedDataCacheKey(feedbackId: number): string {
 | 
				
			||||||
 | 
					        return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':completed:';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns the temporary completion timemodified for the current user.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}    feedbackId      Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {string}    [siteId]        Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}                   Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getCurrentCompletedTimeModified(feedbackId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            const params = {
 | 
				
			||||||
 | 
					                    feedbackid: feedbackId
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                preSets = {
 | 
				
			||||||
 | 
					                    cacheKey: this.getCurrentCompletedTimeModifiedDataCacheKey(feedbackId)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return site.read('mod_feedback_get_current_completed_tmp', params, preSets).then((response) => {
 | 
				
			||||||
 | 
					                if (response && typeof response.feedback != 'undefined' && typeof response.feedback.timemodified != 'undefined') {
 | 
				
			||||||
 | 
					                    return response.feedback.timemodified;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return 0;
 | 
				
			||||||
 | 
					            }).catch(() => {
 | 
				
			||||||
 | 
					                // Ignore errors.
 | 
				
			||||||
 | 
					                return 0;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get prefix cache key for feedback current completed temp data WS calls.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @return {string}         Cache key.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getCurrentCompletedTimeModifiedDataCacheKey(feedbackId: number): string {
 | 
				
			||||||
 | 
					        return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':completedtime:';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns the temporary completion record for the current user.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}    feedbackId          Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {boolean}   [offline=false]     True if it should return cached data. Has priority over ignoreCache.
 | 
				
			||||||
 | 
					     * @param   {boolean}   [ignoreCache=false] True if it should ignore cached data (it always fail in offline or server down).
 | 
				
			||||||
 | 
					     * @param   {string}    [siteId]            Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}                  Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getCurrentValues(feedbackId: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            const params = {
 | 
				
			||||||
 | 
					                    feedbackid: feedbackId
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                preSets = {
 | 
				
			||||||
 | 
					                    cacheKey: this.getCurrentValuesDataCacheKey(feedbackId)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (offline) {
 | 
				
			||||||
 | 
					                preSets['omitExpires'] = true;
 | 
				
			||||||
 | 
					            } else if (ignoreCache) {
 | 
				
			||||||
 | 
					                preSets['getFromCache'] = false;
 | 
				
			||||||
 | 
					                preSets['emergencyCache'] = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return site.read('mod_feedback_get_unfinished_responses', params, preSets).then((response) => {
 | 
				
			||||||
 | 
					                if (response && typeof response.responses != 'undefined') {
 | 
				
			||||||
 | 
					                    return response.responses;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return Promise.reject(null);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get cache key for get current values feedback data WS calls.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} feedbackId  Feedback ID.
 | 
				
			||||||
 | 
					     * @return {string}             Cache key.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getCurrentValuesDataCacheKey(feedbackId: number): string {
 | 
				
			||||||
 | 
					        return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':currentvalues';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get  access information for a given feedback.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}    feedbackId          Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {boolean}   [offline=false]     True if it should return cached data. Has priority over ignoreCache.
 | 
				
			||||||
 | 
					     * @param   {boolean}   [ignoreCache=false] True if it should ignore cached data (it always fail in offline or server down).
 | 
				
			||||||
 | 
					     * @param   {string}    [siteId]            Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}                  Promise resolved when the feedback is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getFeedbackAccessInformation(feedbackId: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string):
 | 
				
			||||||
 | 
					            Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            const params = {
 | 
				
			||||||
 | 
					                    feedbackid: feedbackId
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                preSets = {
 | 
				
			||||||
 | 
					                    cacheKey: this.getFeedbackAccessInformationDataCacheKey(feedbackId)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (offline) {
 | 
				
			||||||
 | 
					                preSets['omitExpires'] = true;
 | 
				
			||||||
 | 
					            } else if (ignoreCache) {
 | 
				
			||||||
 | 
					                preSets['getFromCache'] = false;
 | 
				
			||||||
 | 
					                preSets['emergencyCache'] = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return site.read('mod_feedback_get_feedback_access_information', params, preSets);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get cache key for feedback access information data WS calls.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @return {string}         Cache key.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getFeedbackAccessInformationDataCacheKey(feedbackId: number): string {
 | 
				
			||||||
 | 
					        return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':access';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get cache key for feedback data WS calls.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} courseId Course ID.
 | 
				
			||||||
 | 
					     * @return {string}         Cache key.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getFeedbackCacheKey(courseId: number): string {
 | 
				
			||||||
 | 
					        return this.ROOT_CACHE_KEY + 'feedback:' + courseId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get prefix cache key for all feedback activity data WS calls.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @return {string}         Cache key.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getFeedbackDataPrefixCacheKey(feedbackId: number): string {
 | 
				
			||||||
 | 
					        return this.ROOT_CACHE_KEY + feedbackId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get a feedback with key=value. If more than one is found, only the first will be returned.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number}   courseId            Course ID.
 | 
				
			||||||
 | 
					     * @param {string}   key                 Name of the property to check.
 | 
				
			||||||
 | 
					     * @param {any}      value               Value to search.
 | 
				
			||||||
 | 
					     * @param {string}   [siteId]            Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @param  {boolean} [forceCache=false]  True to always get the value from cache, false otherwise. Default false.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}  Promise resolved when the feedback is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getFeedbackDataByKey(courseId: number, key: string, value: any, siteId?: string, forceCache?: boolean): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            const params = {
 | 
				
			||||||
 | 
					                    courseids: [courseId]
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                preSets = {
 | 
				
			||||||
 | 
					                    cacheKey: this.getFeedbackCacheKey(courseId)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (forceCache) {
 | 
				
			||||||
 | 
					                preSets['omitExpires'] = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return site.read('mod_feedback_get_feedbacks_by_courses', params, preSets).then((response) => {
 | 
				
			||||||
 | 
					                if (response && response.feedbacks) {
 | 
				
			||||||
 | 
					                    const currentFeedback = response.feedbacks.find((feedback) => {
 | 
				
			||||||
 | 
					                        return feedback[key] == value;
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    if (currentFeedback) {
 | 
				
			||||||
 | 
					                        return currentFeedback;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return Promise.reject(null);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get a feedback by course module ID.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number}   courseId       Course ID.
 | 
				
			||||||
 | 
					     * @param {number}   cmId           Course module ID.
 | 
				
			||||||
 | 
					     * @param {string}   [siteId]       Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @param  {boolean} [forceCache]   True to always get the value from cache, false otherwise. Default false.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}   Promise resolved when the feedback is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getFeedback(courseId: number, cmId: number, siteId?: string, forceCache?: boolean): Promise<any> {
 | 
				
			||||||
 | 
					        return this.getFeedbackDataByKey(courseId, 'coursemodule', cmId, siteId, forceCache);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get a feedback by ID.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number}  courseId      Course ID.
 | 
				
			||||||
 | 
					     * @param {number}  id            Feedback ID.
 | 
				
			||||||
 | 
					     * @param {string}  [siteId]      Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @param {boolean} [forceCache]  True to always get the value from cache, false otherwise. Default false.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}         Promise resolved when the feedback is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getFeedbackById(courseId: number, id: number, siteId?: string, forceCache?: boolean): Promise<any> {
 | 
				
			||||||
 | 
					        return this.getFeedbackDataByKey(courseId, 'id', id, siteId, forceCache);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Gets the resume page information.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}    feedbackId          Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {boolean}   [offline=false]     True if it should return cached data. Has priority over ignoreCache.
 | 
				
			||||||
 | 
					     * @param   {boolean}   [ignoreCache=false] True if it should ignore cached data (it always fail in offline or server down).
 | 
				
			||||||
 | 
					     * @param   {string}    [siteId]            Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}                  Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getResumePage(feedbackId: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            const params = {
 | 
				
			||||||
 | 
					                    feedbackid: feedbackId
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                preSets = {
 | 
				
			||||||
 | 
					                    cacheKey: this.getResumePageDataCacheKey(feedbackId)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (offline) {
 | 
				
			||||||
 | 
					                preSets['omitExpires'] = true;
 | 
				
			||||||
 | 
					            } else if (ignoreCache) {
 | 
				
			||||||
 | 
					                preSets['getFromCache'] = false;
 | 
				
			||||||
 | 
					                preSets['emergencyCache'] = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return site.read('mod_feedback_launch_feedback', params, preSets).then((response) => {
 | 
				
			||||||
 | 
					                if (response && typeof response.gopage != 'undefined') {
 | 
				
			||||||
 | 
					                    // WS will return -1 for last page but the user need to start again.
 | 
				
			||||||
 | 
					                    return response.gopage > 0 ? response.gopage : 0;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return Promise.reject(null);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get prefix cache key for resume feedback page data WS calls.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} feedbackId   Feedback ID.
 | 
				
			||||||
 | 
					     * @return {string}             Cache key.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getResumePageDataCacheKey(feedbackId: number): string {
 | 
				
			||||||
 | 
					        return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':launch';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Invalidates feedback data except files and module info.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} feedbackId   Feedback ID.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]     Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}        Promise resolved when the data is invalidated.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateAllFeedbackData(feedbackId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            return site.invalidateWsCacheForKeyStartingWith(this.getFeedbackDataPrefixCacheKey(feedbackId));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Invalidates feedback analysis data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} feedbackId   Feedback ID.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]    Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}        Promise resolved when the data is invalidated.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateAnalysisData(feedbackId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            return site.invalidateWsCacheForKeyStartingWith(this.getAnalysisDataPrefixCacheKey(feedbackId));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Invalidate the prefetched content.
 | 
				
			||||||
 | 
					     * To invalidate files, use AddonFeedbackProvider#invalidateFiles.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} moduleId The module ID.
 | 
				
			||||||
 | 
					     * @param  {number} courseId Course ID of the module.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId] Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}    Promise resolved when the data is invalidated.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        siteId = siteId || this.sitesProvider.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const promises = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        promises.push(this.getFeedback(courseId, moduleId, siteId).then((feedback) => {
 | 
				
			||||||
 | 
					            const ps = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Do not invalidate module data before getting module info, we need it!
 | 
				
			||||||
 | 
					            ps.push(this.invalidateFeedbackData(courseId, siteId));
 | 
				
			||||||
 | 
					            ps.push(this.invalidateAllFeedbackData(feedback.id, siteId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Promise.all(ps);
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        promises.push(this.invalidateFiles(moduleId, siteId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.utils.allPromises(promises);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Invalidates temporary completion record data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} feedbackId   Feedback ID.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]     Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}        Promise resolved when the data is invalidated.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateCurrentValuesData(feedbackId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            return site.invalidateWsCacheForKey(this.getCurrentValuesDataCacheKey(feedbackId));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Invalidates feedback access information data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} feedbackId   Feedback ID.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]    Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}        Promise resolved when the data is invalidated.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateFeedbackAccessInformationData(feedbackId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            return site.invalidateWsCacheForKey(this.getFeedbackAccessInformationDataCacheKey(feedbackId));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Invalidates feedback data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} courseId Course ID.
 | 
				
			||||||
 | 
					     * @param {string} [siteId] Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}   Promise resolved when the data is invalidated.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateFeedbackData(courseId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            return site.invalidateWsCacheForKey(this.getFeedbackCacheKey(courseId));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Invalidate the prefetched files.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} moduleId  The module ID.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId] Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}         Promise resolved when the files are invalidated.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateFiles(moduleId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.filepoolProvider.invalidateFilesByComponent(siteId, AddonModFeedbackProvider.COMPONENT, moduleId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Invalidates launch feedback data.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} feedbackId   Feedback ID.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]    Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}        Promise resolved when the data is invalidated.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateResumePageData(feedbackId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            return site.invalidateWsCacheForKey(this.getResumePageDataCacheKey(feedbackId));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns if feedback has been completed
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}    feedbackId      Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {string}    [siteId]        Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}              Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isCompleted(feedbackId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            const params = {
 | 
				
			||||||
 | 
					                    feedbackid: feedbackId
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                preSets = {
 | 
				
			||||||
 | 
					                    cacheKey: this.getCompletedDataCacheKey(feedbackId)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return this.utils.promiseWorks(site.read('mod_feedback_get_last_completed', params, preSets));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the feedback WS are available.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {string} [siteId] Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<boolean>} Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
 | 
				
			||||||
 | 
					     * @since 3.3
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isPluginEnabled(siteId?: string): Promise<boolean> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            return  site.wsAvailable('mod_feedback_get_feedbacks_by_courses') &&
 | 
				
			||||||
 | 
					                    site.wsAvailable('mod_feedback_get_feedback_access_information');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Report the feedback as being viewed.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} id                   Module ID.
 | 
				
			||||||
 | 
					     * @param  {boolean} [formViewed=false] True if form was viewed.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}               Promise resolved when the WS call is successful.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    logView(id: number, formViewed: boolean = false): Promise<any> {
 | 
				
			||||||
 | 
					        const params = {
 | 
				
			||||||
 | 
					            feedbackid: id,
 | 
				
			||||||
 | 
					            moduleviewed: formViewed ? 1 : 0
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.sitesProvider.getCurrentSite().write('mod_feedback_view_feedback', params);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Process a jump between pages.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param   {number}    feedbackId      Feedback ID.
 | 
				
			||||||
 | 
					     * @param   {number}    page            The page being processed.
 | 
				
			||||||
 | 
					     * @param   {any}       responses       The data to be processed the key is the field name (usually type[index]_id).
 | 
				
			||||||
 | 
					     * @param   {boolean}   goPrevious      Whether we want to jump to previous page.
 | 
				
			||||||
 | 
					     * @param   {string}    [siteId]        Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return  {Promise<any>}                   Promise resolved when the info is retrieved.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    processPageOnline(feedbackId: number, page: number, responses: any, goPrevious: boolean, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            const params = {
 | 
				
			||||||
 | 
					                    feedbackid: feedbackId,
 | 
				
			||||||
 | 
					                    page: page,
 | 
				
			||||||
 | 
					                    responses: this.utils.objectToArrayOfObjects(responses, 'name', 'value'),
 | 
				
			||||||
 | 
					                    goprevious: goPrevious ? 1 : 0
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return site.write('mod_feedback_process_page', params).catch((error) => {
 | 
				
			||||||
 | 
					                return this.utils.createFakeWSError(error);
 | 
				
			||||||
 | 
					            }).then((response) => {
 | 
				
			||||||
 | 
					                // Invalidate and update current values because they will change.
 | 
				
			||||||
 | 
					                return this.invalidateCurrentValuesData(feedbackId, site.getId()).then(() => {
 | 
				
			||||||
 | 
					                    return this.getCurrentValues(feedbackId, false, false, site.getId());
 | 
				
			||||||
 | 
					                }).catch(() => {
 | 
				
			||||||
 | 
					                    // Ignore errors.
 | 
				
			||||||
 | 
					                }).then(() => {
 | 
				
			||||||
 | 
					                    return response;
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										103
									
								
								src/addon/mod/feedback/providers/helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/addon/mod/feedback/providers/helper.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 { NavController } from 'ionic-angular';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Service that provides helper functions for feedbacks.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class AddonModFeedbackHelperProvider {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check if the page we are going to open is in the history and returns the number of pages in the stack to go back.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {string} pageName   Name of the page we want to navigate.
 | 
				
			||||||
 | 
					     * @param {number} instance   Activity instance Id. I.e FeedbackId.
 | 
				
			||||||
 | 
					     * @param {string} paramName  Param name where to find the instance number.
 | 
				
			||||||
 | 
					     * @param {string} prefix     Prefix to check if we are out of the activity context.
 | 
				
			||||||
 | 
					     * @return {number}   Returns the number of times the history needs to go back to find the specified page.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getActivityHistoryBackCounter(pageName: string, instance: number, paramName: string, prefix: string,
 | 
				
			||||||
 | 
					            navCtrl: NavController): number {
 | 
				
			||||||
 | 
					        let historyInstance, params,
 | 
				
			||||||
 | 
					            backTimes = 0,
 | 
				
			||||||
 | 
					            view = navCtrl.getActive();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while (!view.isFirst()) {
 | 
				
			||||||
 | 
					            if (!view.name.startsWith(prefix)) {
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            params = view.getNavParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            historyInstance = params.get(paramName) ? params.get(paramName) : params.get('module').instance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check we are not changing to another activity.
 | 
				
			||||||
 | 
					            if (historyInstance && historyInstance == instance) {
 | 
				
			||||||
 | 
					                backTimes++;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Page found.
 | 
				
			||||||
 | 
					            if (view.name == pageName) {
 | 
				
			||||||
 | 
					                return view.index;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            view = navCtrl.getPrevious(view);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Helper function to open a feature in the app.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {string}        feature   Name of the feature to open.
 | 
				
			||||||
 | 
					     * @param {NavController} navCtrl   NavController.
 | 
				
			||||||
 | 
					     * @param {any}           module    Course module activity object.
 | 
				
			||||||
 | 
					     * @param {number}        courseId  Course Id.
 | 
				
			||||||
 | 
					     * @param {number}        [group=0] Course module activity object.
 | 
				
			||||||
 | 
					     * @return {Promise<void>}    Resolved when navigation animation is done.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    openFeature(feature: string, navCtrl: NavController, module: any, courseId: number, group: number = 0): Promise<void> {
 | 
				
			||||||
 | 
					        const pageName = feature && feature != 'analysis' ? 'AddonModFeedback' + feature + 'Page' : 'AddonModFeedbackIndexPage';
 | 
				
			||||||
 | 
					        let backTimes = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const stateParams = {
 | 
				
			||||||
 | 
					            module: module,
 | 
				
			||||||
 | 
					            moduleId: module.id,
 | 
				
			||||||
 | 
					            courseId: courseId,
 | 
				
			||||||
 | 
					            feedbackId: module.instance,
 | 
				
			||||||
 | 
					            group: group
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Only check history if navigating through tabs.
 | 
				
			||||||
 | 
					        if (pageName == 'AddonModFeedbackIndexPage') {
 | 
				
			||||||
 | 
					            stateParams['tab'] = feature == 'analysis' ? 'analysis' : 'overview';
 | 
				
			||||||
 | 
					            backTimes = this.getActivityHistoryBackCounter(pageName, module.instance, 'feedbackId', 'AddonModFeedback', navCtrl);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (backTimes > 0) {
 | 
				
			||||||
 | 
					            // Go back X times until the the page we want to reach.
 | 
				
			||||||
 | 
					            return navCtrl.remove(navCtrl.getActive().index, backTimes);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Not found, open new state.
 | 
				
			||||||
 | 
					        return navCtrl.push(pageName, stateParams);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										30
									
								
								src/addon/mod/feedback/providers/link-handler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/addon/mod/feedback/providers/link-handler.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 '@core/contentlinks/classes/module-index-handler';
 | 
				
			||||||
 | 
					import { CoreCourseHelperProvider } from '@core/course/providers/helper';
 | 
				
			||||||
 | 
					import { AddonModFeedbackProvider } from './feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Handler to treat links to feedback.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class AddonModFeedbackLinkHandler extends CoreContentLinksModuleIndexHandler {
 | 
				
			||||||
 | 
					    name = 'AddonModFeedbackLinkHandler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(courseHelper: CoreCourseHelperProvider) {
 | 
				
			||||||
 | 
					        super(courseHelper, AddonModFeedbackProvider.COMPONENT, 'feedback');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										71
									
								
								src/addon/mod/feedback/providers/module-handler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/addon/mod/feedback/providers/module-handler.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 { NavController, NavOptions } from 'ionic-angular';
 | 
				
			||||||
 | 
					import { AddonModFeedbackIndexComponent } from '../components/index/index';
 | 
				
			||||||
 | 
					import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate';
 | 
				
			||||||
 | 
					import { CoreCourseProvider } from '@core/course/providers/course';
 | 
				
			||||||
 | 
					import { AddonModFeedbackProvider } from './feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Handler to support feedback modules.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class AddonModFeedbackModuleHandler implements CoreCourseModuleHandler {
 | 
				
			||||||
 | 
					    name = 'feedback';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(private courseProvider: CoreCourseProvider, private feedbackProvider: AddonModFeedbackProvider) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check if the handler is enabled on a site level.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return {Promise<boolean>} Whether or not the handler is enabled on a site level.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isEnabled(): Promise<boolean> {
 | 
				
			||||||
 | 
					        return this.feedbackProvider.isPluginEnabled();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the data required to display the module in the course contents view.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {any} module The module object.
 | 
				
			||||||
 | 
					     * @param {number} courseId The course ID.
 | 
				
			||||||
 | 
					     * @param {number} sectionId The section ID.
 | 
				
			||||||
 | 
					     * @return {CoreCourseModuleHandlerData} Data to render the module.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            icon: this.courseProvider.getModuleIconSrc('feedback'),
 | 
				
			||||||
 | 
					            title: module.name,
 | 
				
			||||||
 | 
					            class: 'addon-mod_feedback-handler',
 | 
				
			||||||
 | 
					            showDownloadButton: true,
 | 
				
			||||||
 | 
					            action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void {
 | 
				
			||||||
 | 
					                navCtrl.push('AddonModFeedbackIndexPage', {module: module, courseId: courseId}, options);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the component to render the module. This is needed to support singleactivity course format.
 | 
				
			||||||
 | 
					     * The component returned must implement CoreCourseModuleMainComponent.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {any} course The course object.
 | 
				
			||||||
 | 
					     * @param {any} module The module object.
 | 
				
			||||||
 | 
					     * @return {any} The component to use, undefined if not found.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getMainComponent(course: any, module: any): any {
 | 
				
			||||||
 | 
					        return AddonModFeedbackIndexComponent;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										163
									
								
								src/addon/mod/feedback/providers/offline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								src/addon/mod/feedback/providers/offline.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,163 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 { CoreLoggerProvider } from '@providers/logger';
 | 
				
			||||||
 | 
					import { CoreSitesProvider } from '@providers/sites';
 | 
				
			||||||
 | 
					import { CoreTextUtilsProvider } from '@providers/utils/text';
 | 
				
			||||||
 | 
					import { CoreTimeUtilsProvider } from '@providers/utils/time';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Service to handle Offline feedback.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class AddonModFeedbackOfflineProvider {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Variables for database.
 | 
				
			||||||
 | 
					    protected FEEDBACK_TABLE = 'mma_mod_feedback_answers';
 | 
				
			||||||
 | 
					    protected tablesSchema = [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            name: this.FEEDBACK_TABLE,
 | 
				
			||||||
 | 
					            columns: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    name: 'feedbackid',
 | 
				
			||||||
 | 
					                    type: 'INTEGER'
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    name: 'page',
 | 
				
			||||||
 | 
					                    type: 'TEXT'
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    name: 'courseid',
 | 
				
			||||||
 | 
					                    type: 'INTEGER'
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    name: 'responses',
 | 
				
			||||||
 | 
					                    type: 'TEXT'
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    name: 'timemodified',
 | 
				
			||||||
 | 
					                    type: 'INTEGER'
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            primaryKeys: ['feedbackid', 'page']
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider,
 | 
				
			||||||
 | 
					        private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) {
 | 
				
			||||||
 | 
					        this.logger = logger.getInstance('AddonModFeedbackOfflineProvider');
 | 
				
			||||||
 | 
					        this.sitesProvider.createTablesFromSchema(this.tablesSchema);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Delete the stored for a certain feedback page.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param  {number} page       Page of the form to delete responses from.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]   Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}      Promise resolved if deleted, rejected if failure.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    deleteFeedbackPageResponses(feedbackId: number, page: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            return site.getDb().deleteRecords(this.FEEDBACK_TABLE, {feedbackid: feedbackId, page: page});
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get all the stored feedback responses data from all the feedback.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {string} [siteId] Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}         Promise resolved with entries.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getAllFeedbackResponses(siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            return site.getDb().getAllRecords(this.FEEDBACK_TABLE).then((entries) => {
 | 
				
			||||||
 | 
					                return entries.map((entry) => {
 | 
				
			||||||
 | 
					                    entry.responses = this.textUtils.parseJSON(entry.responses);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get all the stored responses from a certain feedback.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]   Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}      Promise resolved with responses.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getFeedbackResponses(feedbackId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            return site.getDb().getRecords(this.FEEDBACK_TABLE, {feedbackid: feedbackId});
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the stored responses for a certain feedback page.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param  {number} page       Page of the form to get responses from.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]   Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}      Promise resolved with responses.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getFeedbackPageResponses(feedbackId: number, page: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            return site.getDb().getRecord(this.FEEDBACK_TABLE, {feedbackid: feedbackId, page: page}).then((entry) => {
 | 
				
			||||||
 | 
					                entry.responses = this.textUtils.parseJSON(entry.responses);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return entry;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get if the feedback have something to be synced.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} feedbackId Feedback ID.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]   Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}      Promise resolved with true if the feedback have something to be synced.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    hasFeedbackOfflineData(feedbackId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.getFeedbackResponses(feedbackId, siteId).then((responses) => {
 | 
				
			||||||
 | 
					           return !!responses.length;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Save page responses to be sent later.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} feedbackId   Feedback ID.
 | 
				
			||||||
 | 
					     * @param  {number} page         The page being processed.
 | 
				
			||||||
 | 
					     * @param  {any} responses    The data to be processed the key is the field name (usually type[index]_id)
 | 
				
			||||||
 | 
					     * @param  {number} courseId     Course ID the feedback belongs to.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]     Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}             Promise resolved if stored, rejected if failure.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    saveResponses(feedbackId: number, page: number, responses: any, courseId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.sitesProvider.getSite(siteId).then((site) => {
 | 
				
			||||||
 | 
					            const entry = {
 | 
				
			||||||
 | 
					                    feedbackid: feedbackId,
 | 
				
			||||||
 | 
					                    page: page,
 | 
				
			||||||
 | 
					                    courseid: courseId,
 | 
				
			||||||
 | 
					                    responses: JSON.stringify(responses),
 | 
				
			||||||
 | 
					                    timemodified: this.timeUtils.timestamp()
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return site.getDb().insertOrUpdateRecord(this.FEEDBACK_TABLE, entry, {feedbackid: feedbackId, page: page});
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										226
									
								
								src/addon/mod/feedback/providers/prefetch-handler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								src/addon/mod/feedback/providers/prefetch-handler.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,226 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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, Injector } from '@angular/core';
 | 
				
			||||||
 | 
					import { CoreCourseModulePrefetchHandlerBase } from '@core/course/classes/module-prefetch-handler';
 | 
				
			||||||
 | 
					import { AddonModFeedbackProvider } from './feedback';
 | 
				
			||||||
 | 
					import { AddonModFeedbackHelperProvider } from './helper';
 | 
				
			||||||
 | 
					import { CoreFilepoolProvider } from '@providers/filepool';
 | 
				
			||||||
 | 
					import { CoreTimeUtilsProvider } from '@providers/utils/time';
 | 
				
			||||||
 | 
					import { CoreGroupsProvider } from '@providers/groups';
 | 
				
			||||||
 | 
					import { CoreUserProvider } from '@core/user/providers/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Handler to prefetch feedbacks.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class AddonModFeedbackPrefetchHandler extends CoreCourseModulePrefetchHandlerBase {
 | 
				
			||||||
 | 
					    name = 'feedback';
 | 
				
			||||||
 | 
					    component = AddonModFeedbackProvider.COMPONENT;
 | 
				
			||||||
 | 
					    updatesNames = /^configuration$|^.*files$|^attemptsfinished|^attemptsunfinished$/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(injector: Injector, protected feedbackProvider: AddonModFeedbackProvider,  protected userProvider: CoreUserProvider,
 | 
				
			||||||
 | 
					            protected filepoolProvider: CoreFilepoolProvider, protected feedbackHelper: AddonModFeedbackHelperProvider,
 | 
				
			||||||
 | 
					            protected timeUtils: CoreTimeUtilsProvider, protected groupsProvider: CoreGroupsProvider) {
 | 
				
			||||||
 | 
					        super(injector);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Download or prefetch the content.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {any} module The module object returned by WS.
 | 
				
			||||||
 | 
					     * @param {number} courseId Course ID.
 | 
				
			||||||
 | 
					     * @param {boolean} [prefetch] True to prefetch, false to download right away.
 | 
				
			||||||
 | 
					     * @param {string} [dirPath] Path of the directory where to store all the content files. This is to keep the files
 | 
				
			||||||
 | 
					     *                           relative paths and make the package work in an iframe. Undefined to download the files
 | 
				
			||||||
 | 
					     *                           in the filepool root feedback.
 | 
				
			||||||
 | 
					     * @return {Promise<any>} Promise resolved when all content is downloaded. Data returned is not reliable.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    /*downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string): Promise<any> {
 | 
				
			||||||
 | 
					        const promises = [],
 | 
				
			||||||
 | 
					            siteId = this.sitesProvider.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        promises.push(super.downloadOrPrefetch(module, courseId, prefetch));
 | 
				
			||||||
 | 
					        promises.push(this.feedbackProvider.getFeedback(courseId, module.id).then((feedback) => {
 | 
				
			||||||
 | 
					            const p1 = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            p1.push(this.getFiles(module, courseId).then((files) => {
 | 
				
			||||||
 | 
					                return this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id);
 | 
				
			||||||
 | 
					            }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            p1.push(this.feedbackProvider.getFeedbackAccessInformation(feedback.id, false, true, siteId).then((accessData) => {
 | 
				
			||||||
 | 
					                const p2 = [];
 | 
				
			||||||
 | 
					                if (accessData.canedititems || accessData.canviewreports) {
 | 
				
			||||||
 | 
					                    // Get all groups analysis.
 | 
				
			||||||
 | 
					                    p2.push(this.feedbackProvider.getAnalysis(feedback.id, undefined, siteId));
 | 
				
			||||||
 | 
					                    p2.push(this.groupsProvider.getActivityGroupInfo(feedback.coursemodule, true, undefined, siteId)
 | 
				
			||||||
 | 
					                            .then((groupInfo) => {
 | 
				
			||||||
 | 
					                        const p3 = [],
 | 
				
			||||||
 | 
					                            userIds = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (!groupInfo.groups || groupInfo.groups.length == 0) {
 | 
				
			||||||
 | 
					                            groupInfo.groups = [{id: 0}];
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        groupInfo.groups.forEach((group) => {
 | 
				
			||||||
 | 
					                            p3.push(this.feedbackProvider.getAnalysis(feedback.id, group.id, siteId));
 | 
				
			||||||
 | 
					                            p3.push(this.feedbackProvider.getAllResponsesAnalysis(feedback.id, group.id, siteId)
 | 
				
			||||||
 | 
					                                    .then((responses) => {
 | 
				
			||||||
 | 
					                                responses.attempts.forEach((attempt) => {
 | 
				
			||||||
 | 
					                                    userIds.push(attempt.userid);
 | 
				
			||||||
 | 
					                                });
 | 
				
			||||||
 | 
					                            }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if (!accessData.isanonymous) {
 | 
				
			||||||
 | 
					                                p3.push(this.feedbackProvider.getAllNonRespondents(feedback.id, group.id, siteId)
 | 
				
			||||||
 | 
					                                        .then((responses) => {
 | 
				
			||||||
 | 
					                                    responses.users.forEach((user) => {
 | 
				
			||||||
 | 
					                                        userIds.push(user.userid);
 | 
				
			||||||
 | 
					                                    });
 | 
				
			||||||
 | 
					                                }));
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return Promise.all(p3).then(() => {
 | 
				
			||||||
 | 
					                            // Prefetch user profiles.
 | 
				
			||||||
 | 
					                            return this.userProvider.prefetchProfiles(userIds, courseId, siteId);
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                    }));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                p2.push(this.feedbackProvider.getItems(feedback.id, siteId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (accessData.cancomplete && accessData.cansubmit && !accessData.isempty) {
 | 
				
			||||||
 | 
					                    // Send empty data, so it will recover last completed feedback attempt values.
 | 
				
			||||||
 | 
					                    p2.push(this.feedbackProvider.processPageOnline(feedback.id, 0, {}, undefined, siteId).finally(() => {
 | 
				
			||||||
 | 
					                        const p4 = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        p4.push(this.feedbackProvider.getCurrentValues(feedback.id, false, true, siteId));
 | 
				
			||||||
 | 
					                        p4.push(this.feedbackProvider.getResumePage(feedback.id, false, true, siteId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return Promise.all(p4);
 | 
				
			||||||
 | 
					                    }));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return Promise.all(p2);
 | 
				
			||||||
 | 
					            }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Promise.all(p1);
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Promise.all(promises);
 | 
				
			||||||
 | 
					    }*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the list of downloadable files.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {any} module    Module to get the files.
 | 
				
			||||||
 | 
					     * @param  {number} courseId  Course ID the module belongs to.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]  Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}     Promise resolved with the list of files.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    /*getFiles(module: any, courseId: number, single?: boolean): Promise<any[]> {
 | 
				
			||||||
 | 
					        let files = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.feedbackProvider.getFeedback(courseId, module.id).then((feedback) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get intro files and page after submit files.
 | 
				
			||||||
 | 
					            files = feedback.pageaftersubmitfiles || [];
 | 
				
			||||||
 | 
					            files = files.concat(this.getIntroFilesFromInstance(module, feedback));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return this.feedbackProvider.getItems(feedback.id);
 | 
				
			||||||
 | 
					        }).then((response) => {
 | 
				
			||||||
 | 
					            response.items.forEach((item) => {
 | 
				
			||||||
 | 
					                files = files.concat(item.itemfiles);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return files;
 | 
				
			||||||
 | 
					        }).catch(() => {
 | 
				
			||||||
 | 
					            // Any error, return the list we have.
 | 
				
			||||||
 | 
					            return files;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns feedback intro files.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {any} module The module object returned by WS.
 | 
				
			||||||
 | 
					     * @param {number} courseId Course ID.
 | 
				
			||||||
 | 
					     * @return {Promise<any[]>} Promise resolved with list of intro files.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getIntroFiles(module: any, courseId: number): Promise<any[]> {
 | 
				
			||||||
 | 
					        return this.feedbackProvider.getFeedback(courseId, module.id).catch(() => {
 | 
				
			||||||
 | 
					            // Not found, return undefined so module description is used.
 | 
				
			||||||
 | 
					        }).then((feedback) => {
 | 
				
			||||||
 | 
					            return this.getIntroFilesFromInstance(module, feedback);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Invalidate the prefetched content.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {number} moduleId The module ID.
 | 
				
			||||||
 | 
					     * @param {number} courseId Course ID the module belongs to.
 | 
				
			||||||
 | 
					     * @return {Promise<any>} Promise resolved when the data is invalidated.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateContent(moduleId: number, courseId: number): Promise<any> {
 | 
				
			||||||
 | 
					        return this.feedbackProvider.invalidateContent(moduleId, courseId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Invalidate WS calls needed to determine module status.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {any} module Module.
 | 
				
			||||||
 | 
					     * @param {number} courseId Course ID the module belongs to.
 | 
				
			||||||
 | 
					     * @return {Promise<any>} Promise resolved when invalidated.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    invalidateModule(module: any, courseId: number): Promise<any> {
 | 
				
			||||||
 | 
					        return this.feedbackProvider.invalidateFeedbackData(courseId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check if a feedback is downloadable.
 | 
				
			||||||
 | 
					     * A feedback isn't downloadable if it's not open yet.
 | 
				
			||||||
 | 
					     * Closed feedback are downloadable because teachers can always see the results.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {any} module    Module to check.
 | 
				
			||||||
 | 
					     * @param {number} courseId  Course ID the module belongs to.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}         Promise resolved with true if downloadable, resolved with false otherwise.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isDownloadable(module: any, courseId: number): boolean | Promise<boolean> {
 | 
				
			||||||
 | 
					        return this.feedbackProvider.getFeedback(courseId, module.id, undefined, true).then((feedback) => {
 | 
				
			||||||
 | 
					            const now = this.timeUtils.timestamp();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check time first if available.
 | 
				
			||||||
 | 
					            if (feedback.timeopen && feedback.timeopen > now) {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (feedback.timeclose && feedback.timeclose < now) {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return this.feedbackProvider.getFeedbackAccessInformation(feedback.id).then((accessData) => {
 | 
				
			||||||
 | 
					                return accessData.isopen;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Whether or not the handler is enabled on a site level.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return {boolean|Promise<boolean>} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    isEnabled(): boolean | Promise<boolean> {
 | 
				
			||||||
 | 
					        return this.feedbackProvider.isPluginEnabled();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										47
									
								
								src/addon/mod/feedback/providers/sync-cron-handler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/addon/mod/feedback/providers/sync-cron-handler.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 '@providers/cron';
 | 
				
			||||||
 | 
					import { AddonModFeedbackSyncProvider } from './sync';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Synchronization cron handler.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class AddonModFeedbackSyncCronHandler implements CoreCronHandler {
 | 
				
			||||||
 | 
					    name = 'AddonModFeedbackSyncCronHandler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(private feedbackSync: AddonModFeedbackSyncProvider) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Execute the process.
 | 
				
			||||||
 | 
					     * Receives the ID of the site affected, undefined for all sites.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {string} [siteId] ID of the site affected, undefined for all sites.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}         Promise resolved when done, rejected if failure.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    execute(siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.feedbackSync.syncAllFeedbacks(siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the time between consecutive executions.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return {number} Time between consecutive executions (in ms).
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getInterval(): number {
 | 
				
			||||||
 | 
					        return 600000; // 10 minutes.
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										254
									
								
								src/addon/mod/feedback/providers/sync.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								src/addon/mod/feedback/providers/sync.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,254 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 { CoreLoggerProvider } from '@providers/logger';
 | 
				
			||||||
 | 
					import { CoreSitesProvider } from '@providers/sites';
 | 
				
			||||||
 | 
					import { CoreSyncBaseProvider } from '@classes/base-sync';
 | 
				
			||||||
 | 
					import { CoreAppProvider } from '@providers/app';
 | 
				
			||||||
 | 
					import { CoreUtilsProvider } from '@providers/utils/utils';
 | 
				
			||||||
 | 
					import { CoreTextUtilsProvider } from '@providers/utils/text';
 | 
				
			||||||
 | 
					import { AddonModFeedbackOfflineProvider } from './offline';
 | 
				
			||||||
 | 
					import { AddonModFeedbackProvider } from './feedback';
 | 
				
			||||||
 | 
					import { CoreEventsProvider } from '@providers/events';
 | 
				
			||||||
 | 
					import { TranslateService } from '@ngx-translate/core';
 | 
				
			||||||
 | 
					import { CoreCourseProvider } from '@core/course/providers/course';
 | 
				
			||||||
 | 
					import { CoreSyncProvider } from '@providers/sync';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Service to sync feedbacks.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static AUTO_SYNCED = 'addon_mod_feedback_autom_synced';
 | 
				
			||||||
 | 
					    protected componentTranslate: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(protected sitesProvider: CoreSitesProvider, protected loggerProvider: CoreLoggerProvider,
 | 
				
			||||||
 | 
					            protected appProvider: CoreAppProvider, private feedbackOffline: AddonModFeedbackOfflineProvider,
 | 
				
			||||||
 | 
					            private eventsProvider: CoreEventsProvider,  private feedbackProvider: AddonModFeedbackProvider,
 | 
				
			||||||
 | 
					            private translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider,
 | 
				
			||||||
 | 
					            courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider) {
 | 
				
			||||||
 | 
					        super('AddonModFeedbackSyncProvider', sitesProvider, loggerProvider, appProvider, syncProvider, textUtils);
 | 
				
			||||||
 | 
					        this.componentTranslate = courseProvider.translateModuleName('feedback');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Try to synchronize all the feedbacks in a certain site or in all sites.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {string} [siteId] Site ID to sync. If not defined, sync all sites.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}    Promise resolved if sync is successful, rejected if sync fails.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    syncAllFeedbacks(siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        return this.syncOnSites('all feedbacks', this.syncAllFeedbacksFunc.bind(this), undefined, siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sync all pending feedbacks on a site.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {string} [siteId] Site ID to sync. If not defined, sync all sites.
 | 
				
			||||||
 | 
					     * @param {Promise<any>}     Promise resolved if sync is successful, rejected if sync fails.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected syncAllFeedbacksFunc(siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					         // Sync all new responses.
 | 
				
			||||||
 | 
					        return this.feedbackOffline.getAllFeedbackResponses(siteId).then((responses) => {
 | 
				
			||||||
 | 
					            const promises = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Do not sync same feedback twice.
 | 
				
			||||||
 | 
					            for (const i in responses) {
 | 
				
			||||||
 | 
					                const response = responses[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (typeof promises[response.feedbackid] != 'undefined') {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                promises[response.feedbackid] = this.syncFeedbackIfNeeded(response.feedbackid, siteId).then((result) => {
 | 
				
			||||||
 | 
					                    if (result && result.updated) {
 | 
				
			||||||
 | 
					                        // Sync successful, send event.
 | 
				
			||||||
 | 
					                        this.eventsProvider.trigger(AddonModFeedbackSyncProvider.AUTO_SYNCED, {
 | 
				
			||||||
 | 
					                            feedbackId: response.feedbackid,
 | 
				
			||||||
 | 
					                            userId: response.userid,
 | 
				
			||||||
 | 
					                            warnings: result.warnings
 | 
				
			||||||
 | 
					                        }, siteId);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Promises will be an object so, convert to an array first;
 | 
				
			||||||
 | 
					            return Promise.all(this.utils.objectToArray(promises));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sync a feedback only if a certain time has passed since the last time.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} feedbackId  Feedback ID.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]    Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}       Promise resolved when the feedback is synced or if it doesn't need to be synced.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    syncFeedbackIfNeeded(feedbackId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        siteId = siteId || this.sitesProvider.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.isSyncNeeded(feedbackId, siteId).then((needed) => {
 | 
				
			||||||
 | 
					            if (needed) {
 | 
				
			||||||
 | 
					                return this.syncFeedback(feedbackId, siteId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ynchronize all offline responses of a feedback.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} feedbackId Feedback ID to be synced.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId] Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}    Promise resolved if sync is successful, rejected otherwise.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    syncFeedback(feedbackId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        siteId = siteId || this.sitesProvider.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const syncId = feedbackId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.isSyncing(syncId, siteId)) {
 | 
				
			||||||
 | 
					            // There's already a sync ongoing for this feedback, return the promise.
 | 
				
			||||||
 | 
					            return this.getOngoingSync(syncId, siteId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Verify that feedback isn't blocked.
 | 
				
			||||||
 | 
					        if (this.syncProvider.isBlocked(AddonModFeedbackProvider.COMPONENT, syncId, siteId)) {
 | 
				
			||||||
 | 
					            this.logger.debug(`Cannot sync feedback '${syncId}' because it is blocked.`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return Promise.reject(this.translate.instant('core.errorsyncblocked', {$a: this.componentTranslate}));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const result = {
 | 
				
			||||||
 | 
					            warnings: [],
 | 
				
			||||||
 | 
					            updated: false
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let courseId,
 | 
				
			||||||
 | 
					            feedback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.logger.debug(`Try to sync feedback '${feedbackId}'`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Get offline responses to be sent.
 | 
				
			||||||
 | 
					        const syncPromise = this.feedbackOffline.getFeedbackResponses(feedbackId, siteId).catch(() => {
 | 
				
			||||||
 | 
					            // No offline data found, return empty array.
 | 
				
			||||||
 | 
					            return [];
 | 
				
			||||||
 | 
					        }).then((responses) => {
 | 
				
			||||||
 | 
					            if (!responses.length) {
 | 
				
			||||||
 | 
					                // Nothing to sync.
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!this.appProvider.isOnline()) {
 | 
				
			||||||
 | 
					                // Cannot sync in offline.
 | 
				
			||||||
 | 
					                return Promise.reject(null);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            courseId = responses[0].courseid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return this.feedbackProvider.getFeedbackById(courseId, feedbackId, siteId).then((feedbackData) => {
 | 
				
			||||||
 | 
					                feedback = feedbackData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!feedback.multiple_submit) {
 | 
				
			||||||
 | 
					                    // If it does not admit multiple submits, check if it is completed to know if we can submit.
 | 
				
			||||||
 | 
					                    return this.feedbackProvider.isCompleted(feedbackId);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }).then((isCompleted) => {
 | 
				
			||||||
 | 
					                if (isCompleted) {
 | 
				
			||||||
 | 
					                    // Cannot submit again, delete resposes.
 | 
				
			||||||
 | 
					                    const promises = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    responses.forEach((data) => {
 | 
				
			||||||
 | 
					                        promises.push(this.feedbackOffline.deleteFeedbackPageResponses(feedbackId, data.page, siteId));
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    result.updated = true;
 | 
				
			||||||
 | 
					                    result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
 | 
				
			||||||
 | 
					                        component: this.componentTranslate,
 | 
				
			||||||
 | 
					                        name: feedback.name,
 | 
				
			||||||
 | 
					                        error: this.translate.instant('addon.mod_feedback.this_feedback_is_already_submitted')
 | 
				
			||||||
 | 
					                    }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    return Promise.all(promises);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return this.feedbackProvider.getCurrentCompletedTimeModified(feedbackId, siteId).then((timemodified) => {
 | 
				
			||||||
 | 
					                    // Sort by page.
 | 
				
			||||||
 | 
					                    responses.sort((a, b) => {
 | 
				
			||||||
 | 
					                        return a.page - b.page;
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    responses = responses.map((data) => {
 | 
				
			||||||
 | 
					                        return {
 | 
				
			||||||
 | 
					                            func: this.processPage.bind(this),
 | 
				
			||||||
 | 
					                            params: [feedback, data, siteId, timemodified, result],
 | 
				
			||||||
 | 
					                            blocking: true
 | 
				
			||||||
 | 
					                        };
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Execute all the processes in order to solve dependencies.
 | 
				
			||||||
 | 
					                    return this.utils.executeOrderedPromises(responses);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }).then(() => {
 | 
				
			||||||
 | 
					            if (result.updated) {
 | 
				
			||||||
 | 
					                // Data has been sent to server. Now invalidate the WS calls.
 | 
				
			||||||
 | 
					                return this.feedbackProvider.invalidateAllFeedbackData(feedbackId, siteId).catch(() => {
 | 
				
			||||||
 | 
					                    // Ignore errors.
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }).then(() => {
 | 
				
			||||||
 | 
					            // Sync finished, set sync time.
 | 
				
			||||||
 | 
					            return this.setSyncTime(syncId, siteId);
 | 
				
			||||||
 | 
					        }).then(() => {
 | 
				
			||||||
 | 
					            return result;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.addOngoingSync(syncId, syncPromise, siteId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Convenience function to sync process page calls.
 | 
				
			||||||
 | 
					    protected processPage(feedback: any, data: any, siteId: string, timemodified: number, result: any): Promise<any> {
 | 
				
			||||||
 | 
					        // Delete all pages that are submitted before changing website.
 | 
				
			||||||
 | 
					        if (timemodified > data.timemodified) {
 | 
				
			||||||
 | 
					            return this.feedbackOffline.deleteFeedbackPageResponses(feedback.id, data.page, siteId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.feedbackProvider.processPageOnline(feedback.id, data.page, data.responses, false, siteId).then(() => {
 | 
				
			||||||
 | 
					            result.updated = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return this.feedbackOffline.deleteFeedbackPageResponses(feedback.id, data.page, siteId);
 | 
				
			||||||
 | 
					        }).catch((error) => {
 | 
				
			||||||
 | 
					            if (error && error.wserror) {
 | 
				
			||||||
 | 
					                // The WebService has thrown an error, this means that responses cannot be submitted. Delete them.
 | 
				
			||||||
 | 
					                result.updated = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return this.feedbackOffline.deleteFeedbackPageResponses(feedback.id, data.page, siteId).then(() => {
 | 
				
			||||||
 | 
					                    // Responses deleted, add a warning.
 | 
				
			||||||
 | 
					                    result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
 | 
				
			||||||
 | 
					                        component: this.componentTranslate,
 | 
				
			||||||
 | 
					                        name: feedback.name,
 | 
				
			||||||
 | 
					                        error: error.error
 | 
				
			||||||
 | 
					                    }));
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Couldn't connect to server, reject.
 | 
				
			||||||
 | 
					                return Promise.reject(error && error.error);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -79,6 +79,7 @@ import { AddonFilesModule } from '@addon/files/files.module';
 | 
				
			|||||||
import { AddonModBookModule } from '@addon/mod/book/book.module';
 | 
					import { AddonModBookModule } from '@addon/mod/book/book.module';
 | 
				
			||||||
import { AddonModLabelModule } from '@addon/mod/label/label.module';
 | 
					import { AddonModLabelModule } from '@addon/mod/label/label.module';
 | 
				
			||||||
import { AddonModResourceModule } from '@addon/mod/resource/resource.module';
 | 
					import { AddonModResourceModule } from '@addon/mod/resource/resource.module';
 | 
				
			||||||
 | 
					import { AddonModFeedbackModule } from '@addon/mod/feedback/feedback.module';
 | 
				
			||||||
import { AddonModFolderModule } from '@addon/mod/folder/folder.module';
 | 
					import { AddonModFolderModule } from '@addon/mod/folder/folder.module';
 | 
				
			||||||
import { AddonModPageModule } from '@addon/mod/page/page.module';
 | 
					import { AddonModPageModule } from '@addon/mod/page/page.module';
 | 
				
			||||||
import { AddonModQuizModule } from '@addon/mod/quiz/quiz.module';
 | 
					import { AddonModQuizModule } from '@addon/mod/quiz/quiz.module';
 | 
				
			||||||
@ -173,6 +174,7 @@ export const CORE_PROVIDERS: any[] = [
 | 
				
			|||||||
        AddonModBookModule,
 | 
					        AddonModBookModule,
 | 
				
			||||||
        AddonModLabelModule,
 | 
					        AddonModLabelModule,
 | 
				
			||||||
        AddonModResourceModule,
 | 
					        AddonModResourceModule,
 | 
				
			||||||
 | 
					        AddonModFeedbackModule,
 | 
				
			||||||
        AddonModFolderModule,
 | 
					        AddonModFolderModule,
 | 
				
			||||||
        AddonModPageModule,
 | 
					        AddonModPageModule,
 | 
				
			||||||
        AddonModQuizModule,
 | 
					        AddonModQuizModule,
 | 
				
			||||||
 | 
				
			|||||||
@ -591,6 +591,11 @@ textarea {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					canvas[core-chart] {
 | 
				
			||||||
 | 
					  max-width: 500px;
 | 
				
			||||||
 | 
					  margin: 0 auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.core-circle:before {
 | 
					.core-circle:before {
 | 
				
			||||||
  content: ' \25CF';
 | 
					  content: ' \25CF';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -56,7 +56,7 @@ core-tabs {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.scroll-content.no-scroll {
 | 
					:not(.has-refresher) > .scroll-content.no-scroll {
 | 
				
			||||||
    overflow: hidden !important;
 | 
					    overflow: hidden !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -54,7 +54,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
				
			|||||||
        this.courseProvider = injector.get(CoreCourseProvider);
 | 
					        this.courseProvider = injector.get(CoreCourseProvider);
 | 
				
			||||||
        this.appProvider = injector.get(CoreAppProvider);
 | 
					        this.appProvider = injector.get(CoreAppProvider);
 | 
				
			||||||
        this.eventsProvider = injector.get(CoreEventsProvider);
 | 
					        this.eventsProvider = injector.get(CoreEventsProvider);
 | 
				
			||||||
        this.modulePrefetchProvider = injector.get(CoreCourseModulePrefetchDelegate);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const network = injector.get(Network);
 | 
					        const network = injector.get(Network);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -78,14 +77,7 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
				
			|||||||
        if (this.syncEventName) {
 | 
					        if (this.syncEventName) {
 | 
				
			||||||
            // Refresh data if this discussion is synchronized automatically.
 | 
					            // Refresh data if this discussion is synchronized automatically.
 | 
				
			||||||
            this.syncObserver = this.eventsProvider.on(this.syncEventName, (data) => {
 | 
					            this.syncObserver = this.eventsProvider.on(this.syncEventName, (data) => {
 | 
				
			||||||
                if (this.isRefreshSyncNeeded(data)) {
 | 
					                this.autoSyncEventReceived(data);
 | 
				
			||||||
                    // Refresh the data.
 | 
					 | 
				
			||||||
                    this.loaded = false;
 | 
					 | 
				
			||||||
                    this.refreshIcon = 'spinner';
 | 
					 | 
				
			||||||
                    this.syncIcon = 'spinner';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    this.refreshContent(false);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }, this.siteId);
 | 
					            }, this.siteId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -100,12 +92,7 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise<any> {
 | 
					    doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise<any> {
 | 
				
			||||||
        if (this.loaded) {
 | 
					        if (this.loaded) {
 | 
				
			||||||
            this.refreshIcon = 'spinner';
 | 
					 | 
				
			||||||
            this.syncIcon = 'spinner';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return this.refreshContent(true, showErrors).finally(() => {
 | 
					            return this.refreshContent(true, showErrors).finally(() => {
 | 
				
			||||||
                this.refreshIcon = 'refresh';
 | 
					 | 
				
			||||||
                this.syncIcon = 'sync';
 | 
					 | 
				
			||||||
                refresher && refresher.complete();
 | 
					                refresher && refresher.complete();
 | 
				
			||||||
                done && done();
 | 
					                done && done();
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -124,6 +111,20 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
				
			|||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * An autosync event has been received, check if refresh is needed and update the view.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param {any} syncEventData Data receiven on sync observer.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected autoSyncEventReceived(syncEventData: any): void {
 | 
				
			||||||
 | 
					        if (this.isRefreshSyncNeeded(syncEventData)) {
 | 
				
			||||||
 | 
					            this.loaded = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Refresh the data.
 | 
				
			||||||
 | 
					            this.refreshContent(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Perform the refresh content function.
 | 
					     * Perform the refresh content function.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -132,10 +133,16 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
 | 
				
			|||||||
     * @return {Promise<any>} Resolved when done.
 | 
					     * @return {Promise<any>} Resolved when done.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected refreshContent(sync: boolean = false, showErrors: boolean = false): Promise<any> {
 | 
					    protected refreshContent(sync: boolean = false, showErrors: boolean = false): Promise<any> {
 | 
				
			||||||
 | 
					        this.refreshIcon = 'spinner';
 | 
				
			||||||
 | 
					        this.syncIcon = 'spinner';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.invalidateContent().catch(() => {
 | 
					        return this.invalidateContent().catch(() => {
 | 
				
			||||||
            // Ignore errors.
 | 
					            // Ignore errors.
 | 
				
			||||||
        }).then(() => {
 | 
					        }).then(() => {
 | 
				
			||||||
            return this.loadContent(true, sync, showErrors);
 | 
					            return this.loadContent(true, sync, showErrors);
 | 
				
			||||||
 | 
					        }).finally(() =>  {
 | 
				
			||||||
 | 
					            this.refreshIcon = 'refresh';
 | 
				
			||||||
 | 
					            this.syncIcon = 'sync';
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -79,10 +79,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    doRefresh(refresher?: any, done?: () => void): Promise<any> {
 | 
					    doRefresh(refresher?: any, done?: () => void): Promise<any> {
 | 
				
			||||||
        if (this.loaded) {
 | 
					        if (this.loaded) {
 | 
				
			||||||
            this.refreshIcon = 'spinner';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return this.refreshContent().finally(() => {
 | 
					            return this.refreshContent().finally(() => {
 | 
				
			||||||
                this.refreshIcon = 'refresh';
 | 
					 | 
				
			||||||
                refresher && refresher.complete();
 | 
					                refresher && refresher.complete();
 | 
				
			||||||
                done && done();
 | 
					                done && done();
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -97,10 +94,14 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
				
			|||||||
     * @return {Promise<any>} Resolved when done.
 | 
					     * @return {Promise<any>} Resolved when done.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected refreshContent(): Promise<any> {
 | 
					    protected refreshContent(): Promise<any> {
 | 
				
			||||||
 | 
					        this.refreshIcon = 'spinner';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.invalidateContent().catch(() => {
 | 
					        return this.invalidateContent().catch(() => {
 | 
				
			||||||
            // Ignore errors.
 | 
					            // Ignore errors.
 | 
				
			||||||
        }).then(() => {
 | 
					        }).then(() => {
 | 
				
			||||||
            return this.loadContent(true);
 | 
					            return this.loadContent(true);
 | 
				
			||||||
 | 
					        }).finally(() =>  {
 | 
				
			||||||
 | 
					            this.refreshIcon = 'refresh';
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger';
 | 
				
			|||||||
import { CoreSite } from '@classes/site';
 | 
					import { CoreSite } from '@classes/site';
 | 
				
			||||||
import { CoreSitesProvider } from '@providers/sites';
 | 
					import { CoreSitesProvider } from '@providers/sites';
 | 
				
			||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
 | 
					import { CoreUtilsProvider } from '@providers/utils/utils';
 | 
				
			||||||
 | 
					import { CoreFilepoolProvider } from '@providers/filepool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Service to provide user functionalities.
 | 
					 * Service to provide user functionalities.
 | 
				
			||||||
@ -53,7 +54,8 @@ export class CoreUserProvider {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    protected logger;
 | 
					    protected logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider) {
 | 
					    constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
 | 
				
			||||||
 | 
					            private filepoolProvider: CoreFilepoolProvider) {
 | 
				
			||||||
        this.logger = logger.getInstance('CoreUserProvider');
 | 
					        this.logger = logger.getInstance('CoreUserProvider');
 | 
				
			||||||
        this.sitesProvider.createTablesFromSchema(this.tablesSchema);
 | 
					        this.sitesProvider.createTablesFromSchema(this.tablesSchema);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -366,6 +368,36 @@ export class CoreUserProvider {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Prefetch user profiles and their images from a certain course. It prevents duplicates.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number[]} userIds  List of user IDs.
 | 
				
			||||||
 | 
					     * @param  {number} [courseId] Course the users belong to.
 | 
				
			||||||
 | 
					     * @param  {string} [siteId]   Site ID. If not defined, current site.
 | 
				
			||||||
 | 
					     * @return {Promise<any>}      Promise resolved when prefetched.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    prefetchProfiles(userIds: number[], courseId: number, siteId?: string): Promise<any> {
 | 
				
			||||||
 | 
					        siteId = siteId || this.sitesProvider.getCurrentSiteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const treated = {},
 | 
				
			||||||
 | 
					            promises = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        userIds.forEach((userId) => {
 | 
				
			||||||
 | 
					            // Prevent repeats and errors.
 | 
				
			||||||
 | 
					            if (!treated[userId]) {
 | 
				
			||||||
 | 
					                treated[userId] = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                promises.push(this.getProfile(userId, courseId).then((profile) => {
 | 
				
			||||||
 | 
					                    if (profile.profileimageurl) {
 | 
				
			||||||
 | 
					                        this.filepoolProvider.addToQueueByUrl(siteId, profile.profileimageurl);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Promise.all(promises);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Store user basic information in local DB to be retrieved if the WS call fails.
 | 
					     * Store user basic information in local DB to be retrieved if the WS call fails.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										142
									
								
								src/directives/chart.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/directives/chart.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,142 @@
 | 
				
			|||||||
 | 
					// (C) Copyright 2015 Martin Dougiamas
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// 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 { Directive, Input, OnDestroy, OnInit, ElementRef, OnChanges } from '@angular/core';
 | 
				
			||||||
 | 
					import { Chart } from 'chart.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This component shows a chart using chart.js.
 | 
				
			||||||
 | 
					 * Documentation can be found at http://www.chartjs.org/docs/.
 | 
				
			||||||
 | 
					 * It does not support changes on any input.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Example usage:
 | 
				
			||||||
 | 
					 * <canvas core-chart [data]="data" [labels]="labels" [type]="type" [legend]="legend"></canvas>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Directive({
 | 
				
			||||||
 | 
					    selector: 'canvas[core-chart]'
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					export class CoreChartDirective implements OnDestroy, OnInit, OnChanges {
 | 
				
			||||||
 | 
					    // The first 6 colors will be the app colors, the following will be randomly generated.
 | 
				
			||||||
 | 
					    // It will use the same colors in the whole session.
 | 
				
			||||||
 | 
					    protected static backgroundColors = [
 | 
				
			||||||
 | 
					        'rgba(0,100,210, 0.6)',
 | 
				
			||||||
 | 
					        'rgba(203,61,77, 0.6)',
 | 
				
			||||||
 | 
					        'rgba(0,121,130, 0.6)',
 | 
				
			||||||
 | 
					        'rgba(249,128,18, 0.6)',
 | 
				
			||||||
 | 
					        'rgba(94,129,0, 0.6)',
 | 
				
			||||||
 | 
					        'rgba(251,173,26, 0.6)'
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Input() data: any[]; // Chart data.
 | 
				
			||||||
 | 
					    @Input() labels = []; // Labels of the data.
 | 
				
			||||||
 | 
					    @Input() type: string; // Type of chart.
 | 
				
			||||||
 | 
					    @Input() legend: any; // Legend options.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chart: any;
 | 
				
			||||||
 | 
					    protected element: ElementRef;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(element: ElementRef) {
 | 
				
			||||||
 | 
					        this.element = element;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Component being initialized.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ngOnInit(): any {
 | 
				
			||||||
 | 
					        let legend = {};
 | 
				
			||||||
 | 
					        if (typeof this.legend == 'undefined') {
 | 
				
			||||||
 | 
					            legend = {
 | 
				
			||||||
 | 
					                display: true,
 | 
				
			||||||
 | 
					                position: 'bottom',
 | 
				
			||||||
 | 
					                labels: {
 | 
				
			||||||
 | 
					                    generateLabels: (chart): any => {
 | 
				
			||||||
 | 
					                        const  data = chart.data;
 | 
				
			||||||
 | 
					                        if (data.labels.length && data.labels.length) {
 | 
				
			||||||
 | 
					                            const datasets = data.datasets[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            return data.labels.map((label, i): any => {
 | 
				
			||||||
 | 
					                                return {
 | 
				
			||||||
 | 
					                                    text: label + ': ' + datasets.data[i],
 | 
				
			||||||
 | 
					                                    fillStyle: datasets.backgroundColor[i]
 | 
				
			||||||
 | 
					                                };
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        return [];
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            legend = Object.assign({}, this.legend);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.type == 'bar' && this.data.length >= 5) {
 | 
				
			||||||
 | 
					            this.type = 'horizontalBar';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const context = this.element.nativeElement.getContext('2d');
 | 
				
			||||||
 | 
					        this.chart = new Chart(context, {
 | 
				
			||||||
 | 
					            type: this.type,
 | 
				
			||||||
 | 
					            data: {
 | 
				
			||||||
 | 
					                labels: this.labels,
 | 
				
			||||||
 | 
					                datasets: [{
 | 
				
			||||||
 | 
					                    data:  this.data,
 | 
				
			||||||
 | 
					                    backgroundColor: this.getRandomColors(this.data.length)
 | 
				
			||||||
 | 
					                }]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            options: {legend: legend}
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Listen to chart changes.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ngOnChanges(): void {
 | 
				
			||||||
 | 
					        if (this.chart) {
 | 
				
			||||||
 | 
					            this.chart.data.datasets[0] = {
 | 
				
			||||||
 | 
					                data: this.data,
 | 
				
			||||||
 | 
					                backgroundColor: this.getRandomColors(this.data.length)
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            this.chart.data.labels = this.labels;
 | 
				
			||||||
 | 
					            this.chart.update();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Generate random colors if needed.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param  {number} n Number of colors needed.
 | 
				
			||||||
 | 
					     * @return {any[]}    Array with the number of background colors requested.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected getRandomColors(n: number): any[] {
 | 
				
			||||||
 | 
					        while (CoreChartDirective.backgroundColors.length < n) {
 | 
				
			||||||
 | 
					            const red = Math.floor(Math.random() * 255),
 | 
				
			||||||
 | 
					                green = Math.floor(Math.random() * 255),
 | 
				
			||||||
 | 
					                blue = Math.floor(Math.random() * 255);
 | 
				
			||||||
 | 
					            CoreChartDirective.backgroundColors.push('rgba(' + red + ', ' + green + ', ' + blue + ', 0.6)');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return CoreChartDirective.backgroundColors.slice(0, n);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Component being destroyed.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ngOnDestroy(): any {
 | 
				
			||||||
 | 
					        if (this.chart) {
 | 
				
			||||||
 | 
					            this.chart.destroy();
 | 
				
			||||||
 | 
					            this.chart = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -22,6 +22,7 @@ import { CoreKeepKeyboardDirective } from './keep-keyboard';
 | 
				
			|||||||
import { CoreUserLinkDirective } from './user-link';
 | 
					import { CoreUserLinkDirective } from './user-link';
 | 
				
			||||||
import { CoreAutoRowsDirective } from './auto-rows';
 | 
					import { CoreAutoRowsDirective } from './auto-rows';
 | 
				
			||||||
import { CoreLongPressDirective } from './long-press';
 | 
					import { CoreLongPressDirective } from './long-press';
 | 
				
			||||||
 | 
					import { CoreChartDirective } from './chart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@NgModule({
 | 
					@NgModule({
 | 
				
			||||||
    declarations: [
 | 
					    declarations: [
 | 
				
			||||||
@ -33,7 +34,8 @@ import { CoreLongPressDirective } from './long-press';
 | 
				
			|||||||
        CoreLinkDirective,
 | 
					        CoreLinkDirective,
 | 
				
			||||||
        CoreUserLinkDirective,
 | 
					        CoreUserLinkDirective,
 | 
				
			||||||
        CoreAutoRowsDirective,
 | 
					        CoreAutoRowsDirective,
 | 
				
			||||||
        CoreLongPressDirective
 | 
					        CoreLongPressDirective,
 | 
				
			||||||
 | 
					        CoreChartDirective
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    imports: [],
 | 
					    imports: [],
 | 
				
			||||||
    exports: [
 | 
					    exports: [
 | 
				
			||||||
@ -45,7 +47,8 @@ import { CoreLongPressDirective } from './long-press';
 | 
				
			|||||||
        CoreLinkDirective,
 | 
					        CoreLinkDirective,
 | 
				
			||||||
        CoreUserLinkDirective,
 | 
					        CoreUserLinkDirective,
 | 
				
			||||||
        CoreAutoRowsDirective,
 | 
					        CoreAutoRowsDirective,
 | 
				
			||||||
        CoreLongPressDirective
 | 
					        CoreLongPressDirective,
 | 
				
			||||||
 | 
					        CoreChartDirective
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class CoreDirectivesModule {}
 | 
					export class CoreDirectivesModule {}
 | 
				
			||||||
 | 
				
			|||||||
@ -160,7 +160,7 @@ export class CoreLangProvider {
 | 
				
			|||||||
            return language;
 | 
					            return language;
 | 
				
			||||||
        }).catch(() => {
 | 
					        }).catch(() => {
 | 
				
			||||||
            // User hasn't defined a language. If default language is forced, use it.
 | 
					            // User hasn't defined a language. If default language is forced, use it.
 | 
				
			||||||
            if (!CoreConfigConstants.forcedefaultlanguage) {
 | 
					            if (CoreConfigConstants.default_lang && !CoreConfigConstants.forcedefaultlanguage) {
 | 
				
			||||||
                return CoreConfigConstants.default_lang;
 | 
					                return CoreConfigConstants.default_lang;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -139,6 +139,7 @@ $item-ios-avatar-size: 54px;
 | 
				
			|||||||
$loading-ios-spinner-color: $core-color;
 | 
					$loading-ios-spinner-color: $core-color;
 | 
				
			||||||
$spinner-ios-ios-color: $core-color;
 | 
					$spinner-ios-ios-color: $core-color;
 | 
				
			||||||
$tabs-ios-tab-color-inactive: $tabs-tab-color-inactive;
 | 
					$tabs-ios-tab-color-inactive: $tabs-tab-color-inactive;
 | 
				
			||||||
 | 
					$button-ios-outline-background-color: $white;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// App Material Design Variables
 | 
					// App Material Design Variables
 | 
				
			||||||
@ -152,6 +153,7 @@ $item-md-avatar-size: 54px;
 | 
				
			|||||||
$loading-md-spinner-color: $core-color;
 | 
					$loading-md-spinner-color: $core-color;
 | 
				
			||||||
$spinner-md-crescent-color: $core-color;
 | 
					$spinner-md-crescent-color: $core-color;
 | 
				
			||||||
$tabs-md-tab-color-inactive: $tabs-tab-color-inactive;
 | 
					$tabs-md-tab-color-inactive: $tabs-tab-color-inactive;
 | 
				
			||||||
 | 
					$button-md-outline-background-color: $white;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// App Windows Variables
 | 
					// App Windows Variables
 | 
				
			||||||
@ -164,6 +166,7 @@ $item-wp-avatar-size: 54px;
 | 
				
			|||||||
$loading-wp-spinner-color: $core-color;
 | 
					$loading-wp-spinner-color: $core-color;
 | 
				
			||||||
$spinner-wp-circles-color: $core-color;
 | 
					$spinner-wp-circles-color: $core-color;
 | 
				
			||||||
$tabs-wp-tab-color-inactive: $tabs-tab-color-inactive;
 | 
					$tabs-wp-tab-color-inactive: $tabs-tab-color-inactive;
 | 
				
			||||||
 | 
					$button-wp-outline-background-color: $white;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// App Theme
 | 
					// App Theme
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user