Merge pull request #1308 from dpalou/MOBILE-2376

Mobile 2376
main
Juan Leyva 2018-05-15 10:09:33 +02:00 committed by GitHub
commit bc71bbbdfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 1432 additions and 200 deletions

View File

@ -82,7 +82,7 @@ export interface AddonMessageOutputHandlerData {
constructor(protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider,
protected eventsProvider: CoreEventsProvider) {
super('CoreSettingsDelegate', loggerProvider, sitesProvider, eventsProvider);
super('AddonMessageOutputDelegate', loggerProvider, sitesProvider, eventsProvider);
}
/**

View File

@ -129,7 +129,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
*/
execute(siteId?: string): Promise<any> {
if (this.sitesProvider.isCurrentSite(siteId)) {
this.eventsProvider.trigger(AddonMessagesProvider.READ_CRON_EVENT, undefined, siteId);
this.eventsProvider.trigger(AddonMessagesProvider.READ_CRON_EVENT, {}, siteId);
}
if (this.appProvider.isDesktop() && this.localNotificationsProvider.isAvailable()) {

View File

@ -58,11 +58,12 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
/**
* Get all messages pending to be sent in the site.
* @param {boolean} [onlyDeviceOffline=false] True to only sync discussions that failed because device was offline,
* @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.
*
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
* @param {boolean} [onlyDeviceOffline=false] True to only sync discussions that failed because device was offline.
* @param {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
*/
protected syncAllDiscussionsFunc(onlyDeviceOffline: boolean = false, siteId?: string): Promise<any> {
protected syncAllDiscussionsFunc(siteId?: string, onlyDeviceOffline: boolean = false): Promise<any> {
const promise = onlyDeviceOffline ?
this.messagesOffline.getAllDeviceOfflineMessages(siteId) :
this.messagesOffline.getAllMessages(siteId);

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { AddonModAssignFeedbackHandler } from './feedback-delegate';
@ -24,7 +24,43 @@ export class AddonModAssignDefaultFeedbackHandler implements AddonModAssignFeedb
name = 'AddonModAssignDefaultFeedbackHandler';
type = 'default';
constructor(private translate: TranslateService) { }
constructor(protected translate: TranslateService) { }
/**
* Discard the draft data of the feedback plugin.
*
* @param {number} assignId The assignment ID.
* @param {number} userId User ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} If the function is async, it should return a Promise resolved when done.
*/
discardDraft(assignId: number, userId: number, siteId?: string): void | Promise<any> {
// Nothing to do.
}
/**
* Return the Component to use to display the plugin data.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} plugin The plugin object.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any): any | Promise<any> {
// Nothing to do.
}
/**
* Return the draft saved data of the feedback plugin.
*
* @param {number} assignId The assignment ID.
* @param {number} userId User ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {any|Promise<any>} Data (or promise resolved with the data).
*/
getDraft(assignId: number, userId: number, siteId?: string): any | Promise<any> {
// Nothing to do.
}
/**
* Get files used by this plugin.
@ -95,4 +131,46 @@ export class AddonModAssignDefaultFeedbackHandler implements AddonModAssignFeedb
isEnabled(): boolean | Promise<boolean> {
return true;
}
/**
* Prefetch any required data for the plugin.
* This should NOT prefetch files. Files to be prefetched should be returned by the getPluginFiles function.
*
* @param {any} assign The assignment.
* @param {any} submission The submission.
* @param {any} plugin The plugin object.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise<any> {
return Promise.resolve();
}
/**
* Prepare and add to pluginData the data to send to the server based on the draft data saved.
*
* @param {number} assignId The assignment ID.
* @param {number} userId User ID.
* @param {any} plugin The plugin object.
* @param {any} pluginData Object where to store the data to send.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} If the function is async, it should return a Promise resolved when done.
*/
prepareFeedbackData(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): void | Promise<any> {
// Nothing to do.
}
/**
* Save draft data of the feedback plugin.
*
* @param {number} assignId The assignment ID.
* @param {number} userId User ID.
* @param {any} plugin The plugin object.
* @param {any} data The data to save.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} If the function is async, it should return a Promise resolved when done.
*/
saveDraft(assignId: number, userId: number, plugin: any, data: any, siteId?: string): void | Promise<any> {
// Nothing to do.
}
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { AddonModAssignSubmissionHandler } from './submission-delegate';
@ -24,7 +24,7 @@ export class AddonModAssignDefaultSubmissionHandler implements AddonModAssignSub
name = 'AddonModAssignDefaultSubmissionHandler';
type = 'default';
constructor(private translate: TranslateService) { }
constructor(protected translate: TranslateService) { }
/**
* Whether the plugin can be edited in offline for existing submissions. In general, this should return false if the
@ -40,6 +40,60 @@ export class AddonModAssignDefaultSubmissionHandler implements AddonModAssignSub
return false;
}
/**
* Should clear temporary data for a cancelled submission.
*
* @param {any} assign The assignment.
* @param {any} submission The submission.
* @param {any} plugin The plugin object.
* @param {any} inputData Data entered by the user for the submission.
*/
clearTmpData(assign: any, submission: any, plugin: any, inputData: any): void {
// Nothing to do.
}
/**
* This function will be called when the user wants to create a new submission based on the previous one.
* It should add to pluginData the data to send to server based in the data in plugin (previous attempt).
*
* @param {any} assign The assignment.
* @param {any} plugin The plugin object.
* @param {any} pluginData Object where to store the data to send.
* @param {number} [userId] User ID. If not defined, site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} If the function is async, it should return a Promise resolved when done.
*/
copySubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise<any> {
// Nothing to do.
}
/**
* Delete any stored data for the plugin and submission.
*
* @param {any} assign The assignment.
* @param {any} submission The submission.
* @param {any} plugin The plugin object.
* @param {any} offlineData Offline data stored.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} If the function is async, it should return a Promise resolved when done.
*/
deleteOfflineData(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): void | Promise<any> {
// Nothing to do.
}
/**
* Return the Component to use to display the plugin data, either in read or in edit mode.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} plugin The plugin object.
* @param {boolean} [edit] Whether the user is editing.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise<any> {
// Nothing to do.
}
/**
* Get files used by this plugin.
* The files returned by this function will be prefetched when the user prefetches the assign.
@ -127,4 +181,53 @@ export class AddonModAssignDefaultSubmissionHandler implements AddonModAssignSub
isEnabledForEdit(): boolean | Promise<boolean> {
return false;
}
/**
* Prefetch any required data for the plugin.
* This should NOT prefetch files. Files to be prefetched should be returned by the getPluginFiles function.
*
* @param {any} assign The assignment.
* @param {any} submission The submission.
* @param {any} plugin The plugin object.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise<any> {
return Promise.resolve();
}
/**
* Prepare and add to pluginData the data to send to the server based on the input data.
*
* @param {any} assign The assignment.
* @param {any} submission The submission.
* @param {any} plugin The plugin object.
* @param {any} inputData Data entered by the user for the submission.
* @param {any} pluginData Object where to store the data to send.
* @param {boolean} [offline] Whether the user is editing in offline.
* @param {number} [userId] User ID. If not defined, site's current user.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} If the function is async, it should return a Promise resolved when done.
*/
prepareSubmissionData?(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean,
userId?: number, siteId?: string): void | Promise<any> {
// Nothing to do.
}
/**
* Prepare and add to pluginData the data to send to the server based on the offline data stored.
* This will be used when performing a synchronization.
*
* @param {any} assign The assignment.
* @param {any} submission The submission.
* @param {any} plugin The plugin object.
* @param {any} offlineData Offline data stored.
* @param {any} pluginData Object where to store the data to send.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} If the function is async, it should return a Promise resolved when done.
*/
prepareSyncData?(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string)
: void | Promise<any> {
// Nothing to do.
}
}

View File

@ -24,6 +24,7 @@ import { FormGroup, FormBuilder } from '@angular/forms';
})
export class AddonModQuizAccessOfflineAttemptsComponent implements OnInit {
@Input() rule: string; // The name of the rule.
@Input() quiz: any; // The quiz the rule belongs to.
@Input() attempt: any; // The attempt being started/continued.
@Input() prefetch: boolean; // Whether the user is prefetching the quiz.

View File

@ -24,6 +24,7 @@ import { FormGroup, FormBuilder } from '@angular/forms';
})
export class AddonModQuizAccessPasswordComponent implements OnInit {
@Input() rule: string; // The name of the rule.
@Input() quiz: any; // The quiz the rule belongs to.
@Input() attempt: any; // The attempt being started/continued.
@Input() prefetch: boolean; // Whether the user is prefetching the quiz.

View File

@ -24,6 +24,7 @@ import { FormGroup } from '@angular/forms';
})
export class AddonModQuizAccessTimeLimitComponent {
@Input() rule: string; // The name of the rule.
@Input() quiz: any; // The quiz the rule belongs to.
@Input() attempt: any; // The attempt being started/continued.
@Input() prefetch: boolean; // Whether the user is prefetching the quiz.

View File

@ -12,8 +12,8 @@
<core-loading [hideUntil]="loaded">
<form ion-list [formGroup]="preflightForm" (ngSubmit)="sendData()">
<!-- Access rules. -->
<ng-container *ngFor="let componentClass of accessRulesComponent; let last = last">
<core-dynamic-component [component]="componentClass" [data]="data">
<ng-container *ngFor="let data of accessRulesData; let last = last">
<core-dynamic-component [component]="data.component" [data]="data.data">
<p padding>Couldn't find the directive to render this access rule.</p>
</core-dynamic-component>
<ion-item-divider color="light" *ngIf="!last"></ion-item-divider>

View File

@ -34,8 +34,7 @@ export class AddonModQuizPreflightModalPage implements OnInit {
preflightForm: FormGroup;
title: string;
accessRulesComponent: any[] = [];
data: any;
accessRulesData: {component: any, data: any}[] = []; // Components and data for each access rule.
loaded: boolean;
protected quiz: any;
@ -43,7 +42,6 @@ export class AddonModQuizPreflightModalPage implements OnInit {
protected prefetch: boolean;
protected siteId: string;
protected rules: string[];
protected renderedRules: string[] = [];
constructor(params: NavParams, fb: FormBuilder, translate: TranslateService, sitesProvider: CoreSitesProvider,
protected viewCtrl: ViewController, protected accessRuleDelegate: AddonModQuizAccessRuleDelegate,
@ -58,15 +56,6 @@ export class AddonModQuizPreflightModalPage implements OnInit {
// Create an empty form group. The controls will be added by the access rules components.
this.preflightForm = fb.group({});
// Create the data to pass to the access rules components.
this.data = {
quiz: this.quiz,
attempt: this.attempt,
prefetch: this.prefetch,
form: this.preflightForm,
siteId: this.siteId
};
}
/**
@ -83,8 +72,17 @@ export class AddonModQuizPreflightModalPage implements OnInit {
if (required) {
return this.accessRuleDelegate.getPreflightComponent(rule, this.injector).then((component) => {
if (component) {
this.renderedRules.push(rule);
this.accessRulesComponent.push(component);
this.accessRulesData.push({
component: component,
data: {
rule: rule,
quiz: this.quiz,
attempt: this.attempt,
prefetch: this.prefetch,
form: this.preflightForm,
siteId: this.siteId
}
});
}
});
}

View File

@ -264,6 +264,11 @@ export class AddonPushNotificationsProvider {
return previous + parseInt(counter, 10);
}, 0);
if (!this.appProvider.isDesktop() && !this.appProvider.isMobile()) {
// Browser doesn't have an app badge, stop.
return total;
}
// Set the app badge.
return this.badge.set(total).then(() => {
return total;

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -34,11 +34,12 @@ export class AddonQbehaviourAdaptiveHandler implements CoreQuestionBehaviourHand
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -34,11 +34,12 @@ export class AddonQbehaviourAdaptiveNoPenaltyHandler implements CoreQuestionBeha
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, EventEmitter } from '@angular/core';
import { Component, Input, Output, EventEmitter } from '@angular/core';
/**
* Component to render the deferred CBM in a question.
@ -27,8 +27,8 @@ export class AddonQbehaviourDeferredCBMComponent {
@Input() componentId: number; // ID of the component the question belongs to.
@Input() attemptId: number; // Attempt ID.
@Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
@Input() buttonClicked: EventEmitter<any>; // Should emit an event when a behaviour button is clicked.
@Input() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
@Output() buttonClicked: EventEmitter<any>; // Should emit an event when a behaviour button is clicked.
@Output() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
constructor() {
// Nothing to do.

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionDelegate } from '@core/question/providers/delegate';
import { CoreQuestionState } from '@core/question/providers/question';
@ -55,11 +55,12 @@ export class AddonQbehaviourDeferredCBMHandler implements CoreQuestionBehaviourH
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
if (this.questionHelper.extractQbehaviourCBM(question)) {
return [AddonQbehaviourDeferredCBMComponent];
}

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
import { AddonQbehaviourDeferredCBMComponent } from '@addon/qbehaviour/deferredcbm/component/deferredcbm';
@ -35,11 +35,12 @@ export class AddonQbehaviourImmediateCBMHandler implements CoreQuestionBehaviour
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);
if (this.questionHelper.extractQbehaviourCBM(question)) {

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -34,11 +34,12 @@ export class AddonQbehaviourImmediateFeedbackHandler implements CoreQuestionBeha
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input, EventEmitter } from '@angular/core';
import { Component, Input, Output, EventEmitter } from '@angular/core';
/**
* Component to render a "seen" hidden input for informationitem question behaviour.
@ -27,8 +27,8 @@ export class AddonQbehaviourInformationItemComponent {
@Input() componentId: number; // ID of the component the question belongs to.
@Input() attemptId: number; // Attempt ID.
@Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
@Input() buttonClicked: EventEmitter<any>; // Should emit an event when a behaviour button is clicked.
@Input() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
@Output() buttonClicked: EventEmitter<any>; // Should emit an event when a behaviour button is clicked.
@Output() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
constructor() {
// Nothing to do.

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionProvider, CoreQuestionState } from '@core/question/providers/question';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -52,11 +52,12 @@ export class AddonQbehaviourInformationItemHandler implements CoreQuestionBehavi
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
if (this.questionHelper.extractQbehaviourSeenInput(question)) {
return [AddonQbehaviourInformationItemComponent];
}

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -34,11 +34,12 @@ export class AddonQbehaviourInteractiveHandler implements CoreQuestionBehaviourH
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);

View File

@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from '@core/question/providers/behaviour-delegate';
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
@ -34,11 +34,12 @@ export class AddonQbehaviourInteractiveCountbackHandler implements CoreQuestionB
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Just extract the button, it doesn't need any specific component.
this.questionHelper.extractQbehaviourButtons(question);

View File

@ -133,24 +133,26 @@ export class AddonRemoteThemesProvider {
* Get remote styles of a certain site.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<{fileUrl: string, styles: string}>} Promise resolved with the styles and the URL of the CSS file.
* @return {Promise<{fileUrl: string, styles: string}>} Promise resolved with the styles and the URL of the CSS file,
* resolved with undefined if no styles to load.
*/
get(siteId?: string): Promise<{fileUrl: string, styles: string}> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
let fileUrl;
return this.sitesProvider.getSite(siteId).then((site) => {
const infos = site.getInfo();
let promise,
fileUrl;
if (infos && infos.mobilecssurl) {
fileUrl = infos.mobilecssurl;
if (this.fileProvider.isAvailable()) {
// The file system is available. Download the file and remove old CSS files if needed.
return this.downloadFileAndRemoveOld(siteId, fileUrl);
promise = this.downloadFileAndRemoveOld(siteId, fileUrl);
} else {
// Return the online URL.
return fileUrl;
promise = Promise.resolve(fileUrl);
}
} else {
if (infos && infos.mobilecssurl === '') {
@ -158,20 +160,22 @@ export class AddonRemoteThemesProvider {
this.filepoolProvider.removeFilesByComponent(siteId, AddonRemoteThemesProvider.COMPONENT, 1);
}
return Promise.reject(null);
return;
}
}).then((url) => {
this.logger.debug('Loading styles from: ', url);
// Get the CSS content using HTTP because we will treat the styles before saving them in the file.
return this.http.get(url).toPromise();
}).then((response): any => {
const text = response && response.text();
if (typeof text == 'string') {
return {fileUrl: fileUrl, styles: this.get35Styles(text)};
} else {
return Promise.reject(null);
}
return promise.then((url) => {
this.logger.debug('Loading styles from: ', url);
// Get the CSS content using HTTP because we will treat the styles before saving them in the file.
return this.http.get(url).toPromise();
}).then((response): any => {
const text = response && response.text();
if (typeof text == 'string') {
return {fileUrl: fileUrl, styles: this.get35Styles(text)};
} else {
return Promise.reject(null);
}
});
});
}
@ -208,6 +212,11 @@ export class AddonRemoteThemesProvider {
this.disableElement(this.stylesEls[siteId].element, disabled);
return this.get(siteId).then((data) => {
if (typeof data == 'undefined') {
// Nothing to load.
return;
}
const hash = <string> Md5.hashAsciiStr(data.styles);
// Update the styles only if they have changed.
@ -271,6 +280,8 @@ export class AddonRemoteThemesProvider {
preloadCurrentSite(): Promise<any> {
return this.sitesProvider.getStoredCurrentSiteId().then((siteId) => {
return this.addSite(siteId);
}, () => {
// No current site stored.
});
}

View File

@ -14,11 +14,10 @@
import {
Component, Input, OnInit, OnChanges, OnDestroy, ViewContainerRef, ViewChild, ComponentRef, SimpleChange, ChangeDetectorRef,
ElementRef, Optional
ElementRef, Optional, Output, EventEmitter
} from '@angular/core';
import { NavController } from 'ionic-angular';
import { CoreCompileProvider } from '../../providers/compile';
import { BehaviorSubject } from 'rxjs';
/**
* This component has a behaviour similar to $compile for AngularJS. Given an HTML code, it will compile it so all its
@ -42,19 +41,18 @@ import { BehaviorSubject } from 'rxjs';
export class CoreCompileHtmlComponent implements OnChanges, OnDestroy {
@Input() text: string; // The HTML text to display.
@Input() javascript: string; // The Javascript to execute in the component.
@Input() jsData; // Data to pass to the fake component.
@Input() jsData: any; // Data to pass to the fake component.
@Output() created: EventEmitter<any> = new EventEmitter(); // Will emit an event when the component is instantiated.
// Get the container where to put the content.
@ViewChild('dynamicComponent', { read: ViewContainerRef }) container: ViewContainerRef;
protected componentRef: ComponentRef<any>;
protected element;
componentObservable: BehaviorSubject<any>; // An observable to notify observers when the component is instantiated.
constructor(protected compileProvider: CoreCompileProvider, protected cdr: ChangeDetectorRef, element: ElementRef,
@Optional() protected navCtrl: NavController) {
this.element = element.nativeElement;
this.componentObservable = new BehaviorSubject<any>(null);
}
/**
@ -70,7 +68,7 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy {
if (factory) {
// Create the component.
this.componentRef = this.container.createComponent(factory);
this.componentObservable.next(this.componentRef.instance);
this.created.emit(this.componentRef.instance);
}
});
}
@ -98,13 +96,13 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy {
// If there is some javascript to run, prepare the instance.
if (compileInstance.javascript) {
compileInstance.compileProvider.injectLibraries(this);
// Add some more components and classes.
this['ChangeDetectorRef'] = compileInstance.cdr;
this['NavController'] = compileInstance.navCtrl;
this['componentContainer'] = compileInstance.element;
}
// Always add these elements, they could be needed on component init (componentObservable).
this['ChangeDetectorRef'] = compileInstance.cdr;
this['NavController'] = compileInstance.navCtrl;
this['componentContainer'] = compileInstance.element;
// Add the data passed to the component.
for (const name in compileInstance.jsData) {
this[name] = compileInstance.jsData[name];

View File

@ -75,6 +75,12 @@ import { CoreCourseFormatSingleActivityComponent } from '@core/course/formats/si
import { CoreSitePluginsModuleIndexComponent } from '@core/siteplugins/components/module-index/module-index';
import { CoreSitePluginsCourseOptionComponent } from '@core/siteplugins/components/course-option/course-option';
import { CoreSitePluginsCourseFormatComponent } from '@core/siteplugins/components/course-format/course-format';
import { CoreSitePluginsQuestionComponent } from '@core/siteplugins/components/question/question';
import { CoreSitePluginsQuestionBehaviourComponent } from '@core/siteplugins/components/question-behaviour/question-behaviour';
import { CoreSitePluginsUserProfileFieldComponent } from '@core/siteplugins/components/user-profile-field/user-profile-field';
import { CoreSitePluginsQuizAccessRuleComponent } from '@core/siteplugins/components/quiz-access-rule/quiz-access-rule';
import { CoreSitePluginsAssignFeedbackComponent } from '@core/siteplugins/components/assign-feedback/assign-feedback';
import { CoreSitePluginsAssignSubmissionComponent } from '@core/siteplugins/components/assign-submission/assign-submission';
/**
* Service to provide functionalities regarding compiling dynamic HTML and Javascript.
@ -203,6 +209,12 @@ export class CoreCompileProvider {
instance['CoreSitePluginsModuleIndexComponent'] = CoreSitePluginsModuleIndexComponent;
instance['CoreSitePluginsCourseOptionComponent'] = CoreSitePluginsCourseOptionComponent;
instance['CoreSitePluginsCourseFormatComponent'] = CoreSitePluginsCourseFormatComponent;
instance['CoreSitePluginsQuestionComponent'] = CoreSitePluginsQuestionComponent;
instance['CoreSitePluginsQuestionBehaviourComponent'] = CoreSitePluginsQuestionBehaviourComponent;
instance['CoreSitePluginsUserProfileFieldComponent'] = CoreSitePluginsUserProfileFieldComponent;
instance['CoreSitePluginsQuizAccessRuleComponent'] = CoreSitePluginsQuizAccessRuleComponent;
instance['CoreSitePluginsAssignFeedbackComponent'] = CoreSitePluginsAssignFeedbackComponent;
instance['CoreSitePluginsAssignSubmissionComponent'] = CoreSitePluginsAssignSubmissionComponent;
}
/**

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Input, EventEmitter, Injector } from '@angular/core';
import { Input, Output, EventEmitter, Injector } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -27,8 +27,8 @@ export class CoreQuestionBaseComponent {
@Input() componentId: number; // ID of the component the question belongs to.
@Input() attemptId: number; // Attempt ID.
@Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
@Input() buttonClicked: EventEmitter<any>; // Should emit an event when a behaviour button is clicked.
@Input() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
@Output() buttonClicked: EventEmitter<any>; // Should emit an event when a behaviour button is clicked.
@Output() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
protected logger;
protected questionHelper: CoreQuestionHelperProvider;

View File

@ -123,11 +123,15 @@ export class CoreQuestionComponent implements OnInit {
promise.then(() => {
// Handle behaviour.
this.behaviourDelegate.handleQuestion(this.question.preferredBehaviour, this.question).then((comps) => {
this.behaviourDelegate.handleQuestion(this.injector, this.question.preferredBehaviour, this.question)
.then((comps) => {
this.behaviourComponents = comps;
}).finally(() => {
this.question.html = this.domUtils.removeElementFromHtml(this.question.html, '.im-controls');
this.loaded = true;
});
this.questionHelper.extractQbehaviourRedoButton(this.question);
this.question.html = this.domUtils.removeElementFromHtml(this.question.html, '.im-controls');
// Extract the validation error of the question.
this.question.validationError = this.questionHelper.getValidationErrorFromHtml(this.question.html);
@ -138,8 +142,6 @@ export class CoreQuestionComponent implements OnInit {
// Try to extract the feedback and comment for the question.
this.questionHelper.extractQuestionFeedback(this.question);
this.questionHelper.extractQuestionComment(this.question);
this.loaded = true;
});
}
}).catch(() => {

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
@ -48,11 +48,12 @@ export interface CoreQuestionBehaviourHandler extends CoreDelegateHandler {
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion?(question: any): any[] | Promise<any[]>;
handleQuestion?(injector: Injector, question: any): any[] | Promise<any[]>;
}
/**
@ -90,14 +91,15 @@ export class CoreQuestionBehaviourDelegate extends CoreDelegate {
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return a directive to render it.
*
* @param {Injector} injector Injector.
* @param {string} behaviour Default behaviour.
* @param {any} question The question.
* @return {Promise<any[]>} Promise resolved with components to render some extra data in the question.
*/
handleQuestion(behaviour: string, question: any): Promise<any[]> {
handleQuestion(injector: Injector, behaviour: string, question: any): Promise<any[]> {
behaviour = this.questionDelegate.getBehaviourForQuestion(question, behaviour);
return Promise.resolve(this.executeFunctionOnEnabled(behaviour, 'handleQuestion', [question]));
return Promise.resolve(this.executeFunctionOnEnabled(behaviour, 'handleQuestion', [injector, question]));
}
/**

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { Injectable, Injector } from '@angular/core';
import { CoreQuestionBehaviourHandler } from './behaviour-delegate';
import { CoreQuestionProvider, CoreQuestionState } from '@core/question/providers/question';
@ -46,11 +46,12 @@ export class CoreQuestionBehaviourDefaultHandler implements CoreQuestionBehaviou
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(question: any): any[] | Promise<any[]> {
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
// Nothing to do.
return;
}

View File

@ -0,0 +1,59 @@
// (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 { CoreSitePluginsProvider } from '../providers/siteplugins';
/**
* Base class for components that will display a component using core-compile-html and want to call a
* componentInit function returned by the handler JS.
*/
export class CoreSitePluginsCompileInitComponent {
content = ''; // Content.
jsData: any; // Data to pass to the component.
protected handlerSchema: any; // The handler data.
constructor(protected sitePluginsProvider: CoreSitePluginsProvider) { }
/**
* Function called when the component is created.
*
* @param {any} instance The component instance.
*/
componentCreated(instance: any): void {
// Check if the JS defined an init function.
if (instance && this.handlerSchema && this.handlerSchema.methodJSResult &&
this.handlerSchema.methodJSResult.componentInit) {
this.handlerSchema.methodJSResult.componentInit.apply(instance);
}
}
/**
* Get the handler data.
*
* @param {string} name The name of the handler.
*/
getHandlerData(name: string): void {
// Retrieve the handler data.
const handler = this.sitePluginsProvider.getSitePluginHandler(name);
this.handlerSchema = handler && handler.handlerSchema;
if (this.handlerSchema) {
// Load first template.
if (this.handlerSchema.methodTemplates && this.handlerSchema.methodTemplates.length) {
this.content = handler.handlerSchema.methodTemplates[0].html;
}
}
}
}

View File

@ -0,0 +1,63 @@
// (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 { Injector } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { AddonModAssignDefaultFeedbackHandler } from '@addon/mod/assign/providers/default-feedback-handler';
import { CoreSitePluginsAssignFeedbackComponent } from '../../components/assign-feedback/assign-feedback';
/**
* Handler to display an assign feedback site plugin.
*/
export class CoreSitePluginsAssignFeedbackHandler extends AddonModAssignDefaultFeedbackHandler {
constructor(translate: TranslateService, public name: string, public type: string, protected prefix: string) {
super(translate);
}
/**
* Return the Component to use to display the plugin data, either in read or in edit mode.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} plugin The plugin object.
* @param {boolean} [edit] Whether the user is editing.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise<any> {
return CoreSitePluginsAssignFeedbackComponent;
}
/**
* Get a readable name to use for the plugin.
*
* @param {any} plugin The plugin object.
* @return {string} The plugin name.
*/
getPluginName(plugin: any): string {
// Check if there's a translated string for the plugin.
const translationId = this.prefix + 'pluginname',
translation = this.translate.instant(translationId);
if (translationId != translation) {
// Translation found, use it.
return translation;
}
// Fallback to WS string.
if (plugin.name) {
return plugin.name;
}
}
}

View File

@ -0,0 +1,72 @@
// (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 { Injector } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { AddonModAssignDefaultSubmissionHandler } from '@addon/mod/assign/providers/default-submission-handler';
import { CoreSitePluginsAssignSubmissionComponent } from '../../components/assign-submission/assign-submission';
/**
* Handler to display an assign submission site plugin.
*/
export class CoreSitePluginsAssignSubmissionHandler extends AddonModAssignDefaultSubmissionHandler {
constructor(translate: TranslateService, public name: string, public type: string, protected prefix: string) {
super(translate);
}
/**
* Return the Component to use to display the plugin data, either in read or in edit mode.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} plugin The plugin object.
* @param {boolean} [edit] Whether the user is editing.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise<any> {
return CoreSitePluginsAssignSubmissionComponent;
}
/**
* Get a readable name to use for the plugin.
*
* @param {any} plugin The plugin object.
* @return {string} The plugin name.
*/
getPluginName(plugin: any): string {
// Check if there's a translated string for the plugin.
const translationId = this.prefix + 'pluginname',
translation = this.translate.instant(translationId);
if (translationId != translation) {
// Translation found, use it.
return translation;
}
// Fallback to WS string.
if (plugin.name) {
return plugin.name;
}
}
/**
* Whether or not the handler is enabled for edit on a site level.
*
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled for edit on a site level.
*/
isEnabledForEdit(): boolean | Promise<boolean> {
return true;
}
}

View File

@ -15,7 +15,7 @@
import { Injector } from '@angular/core';
import { CoreCourseFormatHandler } from '@core/course/providers/format-delegate';
import { CoreSitePluginsBaseHandler } from './base-handler';
import { CoreSitePluginsCourseFormatComponent } from '../components/course-format/course-format';
import { CoreSitePluginsCourseFormatComponent } from '../../components/course-format/course-format';
/**
* Handler to support a course format using a site plugin.

View File

@ -13,10 +13,10 @@
// limitations under the License.
import { Injector } from '@angular/core';
import { CoreSitePluginsProvider } from '../providers/siteplugins';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreCourseOptionsHandler, CoreCourseOptionsHandlerData } from '@core/course/providers/options-delegate';
import { CoreSitePluginsBaseHandler } from './base-handler';
import { CoreSitePluginsCourseOptionComponent } from '../components/course-option/course-option';
import { CoreSitePluginsCourseOptionComponent } from '../../components/course-option/course-option';
/**
* Handler to display a site plugin in course options.

View File

@ -25,7 +25,8 @@ export class CoreSitePluginsMainMenuHandler extends CoreSitePluginsBaseHandler i
protected initResult: any) {
super(name);
this.priority = handlerSchema.priority;
// Set 699 as max priority so site plugins are always shown in the More tab (700 is Notifications tab).
this.priority = Math.min(handlerSchema.priority, 699);
}
/**

View 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 { AddonMessageOutputHandler, AddonMessageOutputHandlerData } from '@addon/messageoutput/providers/delegate';
import { CoreSitePluginsBaseHandler } from './base-handler';
/**
* Handler to display a message output settings option.
*/
export class CoreSitePluginsMessageOutputHandler extends CoreSitePluginsBaseHandler implements AddonMessageOutputHandler {
constructor(name: string, public processorName: string, protected title: string, protected plugin: any,
protected handlerSchema: any, protected initResult: any) {
super(name);
}
/**
* Returns the data needed to render the handler.
*
* @return {AddonMessageOutputHandlerData} Data.
*/
getDisplayData(): AddonMessageOutputHandlerData {
return {
priority: this.handlerSchema.priority,
label: this.title,
icon: this.handlerSchema.displaydata.icon,
page: 'CoreSitePluginsPluginPage',
pageParams: {
title: this.title,
component: this.plugin.component,
method: this.handlerSchema.method,
initResult: this.initResult
}
};
}
}

View File

@ -16,7 +16,7 @@ import { Injector } from '@angular/core';
import { NavController, NavOptions } from 'ionic-angular';
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate';
import { CoreSitePluginsBaseHandler } from './base-handler';
import { CoreSitePluginsModuleIndexComponent } from '../components/module-index/module-index';
import { CoreSitePluginsModuleIndexComponent } from '../../components/module-index/module-index';
/**
* Handler to support a module using a site plugin.

View File

@ -13,7 +13,7 @@
// limitations under the License.
import { Injector } from '@angular/core';
import { CoreSitePluginsProvider } from '../providers/siteplugins';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreCourseModulePrefetchHandlerBase } from '@core/course/classes/module-prefetch-handler';
/**

View File

@ -0,0 +1,44 @@
// (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 { Injector } from '@angular/core';
import { CoreQuestionBehaviourDefaultHandler } from '@core/question/providers/default-behaviour-handler';
import { CoreSitePluginsQuestionBehaviourComponent } from '../../components/question-behaviour/question-behaviour';
import { CoreQuestionProvider } from '@core/question/providers/question';
/**
* Handler to display a question behaviour site plugin.
*/
export class CoreSitePluginsQuestionBehaviourHandler extends CoreQuestionBehaviourDefaultHandler {
constructor(questionProvider: CoreQuestionProvider, public name: string, public type: string, public hasTemplate: boolean) {
super(questionProvider);
}
/**
* Handle a question behaviour.
* If the behaviour requires a submit button, it should add it to question.behaviourButtons.
* If the behaviour requires to show some extra data, it should return the components to render it.
*
* @param {Injector} injector Injector.
* @param {any} question The question.
* @return {any[]|Promise<any[]>} Components (or promise resolved with components) to render some extra data in the question
* (e.g. certainty options). Don't return anything if no extra data is required.
*/
handleQuestion(injector: Injector, question: any): any[] | Promise<any[]> {
if (this.hasTemplate) {
return [CoreSitePluginsQuestionBehaviourComponent];
}
}
}

View File

@ -0,0 +1,39 @@
// (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 { Injector } from '@angular/core';
import { CoreQuestionDefaultHandler } from '@core/question/providers/default-question-handler';
import { CoreSitePluginsQuestionComponent } from '../../components/question/question';
/**
* Handler to display a question site plugin.
*/
export class CoreSitePluginsQuestionHandler extends CoreQuestionDefaultHandler {
constructor(public name: string, public type: string) {
super();
}
/**
* Return the Component to use to display the question.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @param {any} question The question to render.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector): any | Promise<any> {
return CoreSitePluginsQuestionComponent;
}
}

View File

@ -0,0 +1,110 @@
// (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 { Injector } from '@angular/core';
import { CoreQuestionDefaultHandler } from '@core/question/providers/default-question-handler';
import { CoreSitePluginsQuizAccessRuleComponent } from '../../components/quiz-access-rule/quiz-access-rule';
/**
* Handler to display a quiz access rule site plugin.
*/
export class CoreSitePluginsQuizAccessRuleHandler extends CoreQuestionDefaultHandler {
constructor(public name: string, public ruleName: string, public hasTemplate: boolean) {
super();
}
/**
* Whether the rule requires a preflight check when prefetch/start/continue an attempt.
*
* @param {any} quiz The quiz the rule belongs to.
* @param {any} [attempt] The attempt started/continued. If not supplied, user is starting a new attempt.
* @param {boolean} [prefetch] Whether the user is prefetching the quiz.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {boolean|Promise<boolean>} Whether the rule requires a preflight check.
*/
isPreflightCheckRequired(quiz: any, attempt?: any, prefetch?: boolean, siteId?: string): boolean | Promise<boolean> {
return this.hasTemplate;
}
/**
* Add preflight data that doesn't require user interaction. The data should be added to the preflightData param.
*
* @param {any} quiz The quiz the rule belongs to.
* @param {any} preflightData Object where to add the preflight data.
* @param {any} [attempt] The attempt started/continued. If not supplied, user is starting a new attempt.
* @param {boolean} [prefetch] Whether the user is prefetching the quiz.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} Promise resolved when done if async, void if it's synchronous.
*/
getFixedPreflightData(quiz: any, preflightData: any, attempt?: any, prefetch?: boolean, siteId?: string): void | Promise<any> {
// Nothing to do.
}
/**
* Return the Component to use to display the access rule preflight.
* Implement this if your access rule requires a preflight check with user interaction.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param {Injector} injector Injector.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getPreflightComponent(injector: Injector): any | Promise<any> {
if (this.hasTemplate) {
return CoreSitePluginsQuizAccessRuleComponent;
}
}
/**
* Function called when the preflight check has passed. This is a chance to record that fact in some way.
*
* @param {any} quiz The quiz the rule belongs to.
* @param {any} attempt The attempt started/continued.
* @param {any} preflightData Preflight data gathered.
* @param {boolean} [prefetch] Whether the user is prefetching the quiz.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} Promise resolved when done if async, void if it's synchronous.
*/
notifyPreflightCheckPassed(quiz: any, attempt: any, preflightData: any, prefetch?: boolean, siteId?: string)
: void | Promise<any> {
// Nothing to do.
}
/**
* Function called when the preflight check fails. This is a chance to record that fact in some way.
*
* @param {any} quiz The quiz the rule belongs to.
* @param {any} attempt The attempt started/continued.
* @param {any} preflightData Preflight data gathered.
* @param {boolean} [prefetch] Whether the user is prefetching the quiz.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} Promise resolved when done if async, void if it's synchronous.
*/
notifyPreflightCheckFailed(quiz: any, attempt: any, preflightData: any, prefetch?: boolean, siteId?: string)
: void | Promise<any> {
// Nothing to do.
}
/**
* Whether or not the time left of an attempt should be displayed.
*
* @param {any} attempt The attempt.
* @param {number} endTime The attempt end time (in seconds).
* @param {number} timeNow The current time in seconds.
* @return {boolean} Whether it should be displayed.
*/
shouldShowTimeLeft(attempt: any, endTime: number, timeNow: number): boolean {
return false;
}
}

View File

@ -0,0 +1,50 @@
// (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 { CoreSettingsHandler, CoreSettingsHandlerData } from '@core/settings/providers/delegate';
import { CoreSitePluginsBaseHandler } from './base-handler';
/**
* Handler to display a site plugin in the settings.
*/
export class CoreSitePluginsSettingsHandler extends CoreSitePluginsBaseHandler implements CoreSettingsHandler {
priority: number;
constructor(name: string, protected title: string, protected plugin: any, protected handlerSchema: any,
protected initResult: any) {
super(name);
this.priority = handlerSchema.priority;
}
/**
* Returns the data needed to render the handler.
*
* @return {CoreSettingsHandlerData} Data.
*/
getDisplayData(): CoreSettingsHandlerData {
return {
title: this.title,
icon: this.handlerSchema.displaydata.icon,
class: this.handlerSchema.displaydata.class,
page: 'CoreSitePluginsPluginPage',
params: {
title: this.title,
component: this.plugin.component,
method: this.handlerSchema.method,
initResult: this.initResult
}
};
}
}

View File

@ -14,7 +14,7 @@
import { NavController } from 'ionic-angular';
import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@core/user/providers/user-delegate';
import { CoreSitePluginsProvider } from '../providers/siteplugins';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreSitePluginsBaseHandler } from './base-handler';
/**

View File

@ -15,7 +15,7 @@
import { Injector } from '@angular/core';
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '@core/user/providers/user-profile-field-delegate';
import { CoreSitePluginsBaseHandler } from './base-handler';
import { CoreSitePluginsUserProfileFieldComponent } from '../components/user-profile-field/user-profile-field';
import { CoreSitePluginsUserProfileFieldComponent } from '../../components/user-profile-field/user-profile-field';
/**
* Handler to display a site plugin in the user profile.

View File

@ -0,0 +1 @@
<core-compile-html [text]="content" [jsData]="jsData" (created)="componentCreated($event)"></core-compile-html>

View File

@ -0,0 +1,67 @@
// (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, OnInit, Input } from '@angular/core';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreSitePluginsCompileInitComponent } from '../../classes/compile-init-component';
/**
* Component that displays an assign feedback plugin created using a site plugin.
*/
@Component({
selector: 'core-site-plugins-assign-feedback',
templateUrl: 'assign-feedback.html',
})
export class CoreSitePluginsAssignFeedbackComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
@Input() assign: any; // The assignment.
@Input() submission: any; // The submission.
@Input() plugin: any; // The plugin object.
@Input() userId: number; // The user ID of the submission.
@Input() configs: any; // The configs for the plugin.
@Input() canEdit: boolean; // Whether the user can edit.
@Input() edit: boolean; // Whether the user is editing.
constructor(sitePluginsProvider: CoreSitePluginsProvider) {
super(sitePluginsProvider);
}
/**
* Component being initialized.
*/
ngOnInit(): void {
// Pass the input and output data to the component.
this.jsData = {
assign: this.assign,
submission: this.submission,
plugin: this.plugin,
userId: this.userId,
configs: this.configs,
edit: this.edit,
canEdit: this.canEdit
};
if (this.plugin) {
this.getHandlerData('assignfeedback_' + this.plugin.type);
}
}
/**
* Invalidate the data.
*
* @return {Promise<any>} Promise resolved when done.
*/
invalidate(): Promise<any> {
return Promise.resolve();
}
}

View File

@ -0,0 +1 @@
<core-compile-html [text]="content" [jsData]="jsData" (created)="componentCreated($event)"></core-compile-html>

View File

@ -0,0 +1,65 @@
// (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, OnInit, Input } from '@angular/core';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreSitePluginsCompileInitComponent } from '../../classes/compile-init-component';
/**
* Component that displays an assign submission plugin created using a site plugin.
*/
@Component({
selector: 'core-site-plugins-assign-submission',
templateUrl: 'assign-submission.html',
})
export class CoreSitePluginsAssignSubmissionComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
@Input() assign: any; // The assignment.
@Input() submission: any; // The submission.
@Input() plugin: any; // The plugin object.
@Input() configs: any; // The configs for the plugin.
@Input() edit: boolean; // Whether the user is editing.
@Input() allowOffline: boolean; // Whether to allow offline.
constructor(sitePluginsProvider: CoreSitePluginsProvider) {
super(sitePluginsProvider);
}
/**
* Component being initialized.
*/
ngOnInit(): void {
// Pass the input and output data to the component.
this.jsData = {
assign: this.assign,
submission: this.submission,
plugin: this.plugin,
configs: this.configs,
edit: this.edit,
allowOffline: this.allowOffline
};
if (this.plugin) {
this.getHandlerData('assignsubmission_' + this.plugin.type);
}
}
/**
* Invalidate the data.
*
* @return {Promise<any>} Promise resolved when done.
*/
invalidate(): Promise<any> {
return Promise.resolve();
}
}

View File

@ -23,6 +23,11 @@ import { CoreSitePluginsModuleIndexComponent } from './module-index/module-index
import { CoreSitePluginsCourseOptionComponent } from './course-option/course-option';
import { CoreSitePluginsCourseFormatComponent } from './course-format/course-format';
import { CoreSitePluginsUserProfileFieldComponent } from './user-profile-field/user-profile-field';
import { CoreSitePluginsQuestionComponent } from './question/question';
import { CoreSitePluginsQuestionBehaviourComponent } from './question-behaviour/question-behaviour';
import { CoreSitePluginsQuizAccessRuleComponent } from './quiz-access-rule/quiz-access-rule';
import { CoreSitePluginsAssignFeedbackComponent } from './assign-feedback/assign-feedback';
import { CoreSitePluginsAssignSubmissionComponent } from './assign-submission/assign-submission';
@NgModule({
declarations: [
@ -30,7 +35,12 @@ import { CoreSitePluginsUserProfileFieldComponent } from './user-profile-field/u
CoreSitePluginsModuleIndexComponent,
CoreSitePluginsCourseOptionComponent,
CoreSitePluginsCourseFormatComponent,
CoreSitePluginsUserProfileFieldComponent
CoreSitePluginsUserProfileFieldComponent,
CoreSitePluginsQuestionComponent,
CoreSitePluginsQuestionBehaviourComponent,
CoreSitePluginsQuizAccessRuleComponent,
CoreSitePluginsAssignFeedbackComponent,
CoreSitePluginsAssignSubmissionComponent
],
imports: [
CommonModule,
@ -46,13 +56,23 @@ import { CoreSitePluginsUserProfileFieldComponent } from './user-profile-field/u
CoreSitePluginsModuleIndexComponent,
CoreSitePluginsCourseOptionComponent,
CoreSitePluginsCourseFormatComponent,
CoreSitePluginsUserProfileFieldComponent
CoreSitePluginsUserProfileFieldComponent,
CoreSitePluginsQuestionComponent,
CoreSitePluginsQuestionBehaviourComponent,
CoreSitePluginsQuizAccessRuleComponent,
CoreSitePluginsAssignFeedbackComponent,
CoreSitePluginsAssignSubmissionComponent
],
entryComponents: [
CoreSitePluginsModuleIndexComponent,
CoreSitePluginsCourseOptionComponent,
CoreSitePluginsCourseFormatComponent,
CoreSitePluginsUserProfileFieldComponent
CoreSitePluginsUserProfileFieldComponent,
CoreSitePluginsQuestionComponent,
CoreSitePluginsQuestionBehaviourComponent,
CoreSitePluginsQuizAccessRuleComponent,
CoreSitePluginsAssignFeedbackComponent,
CoreSitePluginsAssignSubmissionComponent
]
})
export class CoreSitePluginsComponentsModule {}

View File

@ -0,0 +1 @@
<core-compile-html [text]="content" [jsData]="jsData" (created)="componentCreated($event)"></core-compile-html>

View File

@ -0,0 +1,58 @@
// (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, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreSitePluginsCompileInitComponent } from '../../classes/compile-init-component';
/**
* Component that displays a question behaviour created using a site plugin.
*/
@Component({
selector: 'core-site-plugins-question-behaviour',
templateUrl: 'question-behaviour.html',
})
export class CoreSitePluginsQuestionBehaviourComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
@Input() question: any; // The question where the behaviour will be rendered.
@Input() component: string; // The component the question belongs to.
@Input() componentId: number; // ID of the component the question belongs to.
@Input() attemptId: number; // Attempt ID.
@Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
@Output() buttonClicked: EventEmitter<any>; // Should emit an event when a behaviour button is clicked.
@Output() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
constructor(sitePluginsProvider: CoreSitePluginsProvider) {
super(sitePluginsProvider);
}
/**
* Component being initialized.
*/
ngOnInit(): void {
// Pass the input and output data to the component.
this.jsData = {
question: this.question,
component: this.component,
componentId: this.componentId,
attemptId: this.attemptId,
offlineEnabled: this.offlineEnabled,
buttonClicked: this.buttonClicked,
onAbort: this.onAbort
};
if (this.question) {
this.getHandlerData('qbehaviour_' + this.question.preferredBehaviour);
}
}
}

View File

@ -0,0 +1 @@
<core-compile-html [text]="content" [jsData]="jsData" (created)="componentCreated($event)"></core-compile-html>

View File

@ -0,0 +1,58 @@
// (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, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreSitePluginsCompileInitComponent } from '../../classes/compile-init-component';
/**
* Component that displays a question created using a site plugin.
*/
@Component({
selector: 'core-site-plugins-question',
templateUrl: 'question.html',
})
export class CoreSitePluginsQuestionComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
@Input() question: any; // The question to render.
@Input() component: string; // The component the question belongs to.
@Input() componentId: number; // ID of the component the question belongs to.
@Input() attemptId: number; // Attempt ID.
@Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline.
@Output() buttonClicked: EventEmitter<any>; // Should emit an event when a behaviour button is clicked.
@Output() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
constructor(sitePluginsProvider: CoreSitePluginsProvider) {
super(sitePluginsProvider);
}
/**
* Component being initialized.
*/
ngOnInit(): void {
// Pass the input and output data to the component.
this.jsData = {
question: this.question,
component: this.component,
componentId: this.componentId,
attemptId: this.attemptId,
offlineEnabled: this.offlineEnabled,
buttonClicked: this.buttonClicked,
onAbort: this.onAbort
};
if (this.question) {
this.getHandlerData('qtype_' + this.question.type);
}
}
}

View File

@ -0,0 +1 @@
<core-compile-html [text]="content" [jsData]="jsData" (created)="componentCreated($event)"></core-compile-html>

View 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 { Component, OnInit, Input } from '@angular/core';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreSitePluginsCompileInitComponent } from '../../classes/compile-init-component';
import { FormGroup } from '@angular/forms';
/**
* Component that displays a quiz access rule created using a site plugin.
*/
@Component({
selector: 'core-site-plugins-quiz-access-rule',
templateUrl: 'quiz-access-rule.html',
})
export class CoreSitePluginsQuizAccessRuleComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
@Input() rule: string; // The name of the rule.
@Input() quiz: any; // The quiz the rule belongs to.
@Input() attempt: any; // The attempt being started/continued.
@Input() prefetch: boolean; // Whether the user is prefetching the quiz.
@Input() siteId: string; // Site ID.
@Input() form: FormGroup; // Form where to add the form control.
constructor(sitePluginsProvider: CoreSitePluginsProvider) {
super(sitePluginsProvider);
}
/**
* Component being initialized.
*/
ngOnInit(): void {
// Pass the input and output data to the component.
this.jsData = {
rule: this.rule,
quiz: this.quiz,
attempt: this.attempt,
prefetch: this.prefetch,
siteId: this.siteId,
form: this.form
};
if (this.rule) {
this.getHandlerData(this.rule);
}
}
}

View File

@ -1 +1 @@
<core-compile-html [text]="content" [jsData]="jsData"></core-compile-html>
<core-compile-html [text]="content" [jsData]="jsData" (created)="componentCreated($event)"></core-compile-html>

View File

@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, Input, ViewChild, OnDestroy } from '@angular/core';
import { Component, OnInit, Input } from '@angular/core';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreCompileHtmlComponent } from '@core/compile/components/compile-html/compile-html';
import { Subscription } from 'rxjs';
import { CoreSitePluginsCompileInitComponent } from '../../classes/compile-init-component';
/**
* Component that displays a user profile field created using a site plugin.
@ -24,20 +23,16 @@ import { Subscription } from 'rxjs';
selector: 'core-site-plugins-user-profile-field',
templateUrl: 'user-profile-field.html',
})
export class CoreSitePluginsUserProfileFieldComponent implements OnInit, OnDestroy {
export class CoreSitePluginsUserProfileFieldComponent extends CoreSitePluginsCompileInitComponent implements OnInit {
@Input() field: any; // The profile field to be rendered.
@Input() signup = false; // True if editing the field in signup. Defaults to false.
@Input() edit = false; // True if editing the field. Defaults to false.
@Input() form?: any; // Form where to add the form control. Required if edit=true or signup=true.
@Input() registerAuth?: string; // Register auth method. E.g. 'email'.
@ViewChild(CoreCompileHtmlComponent) compileComponent: CoreCompileHtmlComponent;
content = ''; // Content.
jsData;
protected componentObserver: Subscription;
constructor(protected sitePluginsProvider: CoreSitePluginsProvider) { }
constructor(sitePluginsProvider: CoreSitePluginsProvider) {
super(sitePluginsProvider);
}
/**
* Component being initialized.
@ -54,34 +49,7 @@ export class CoreSitePluginsUserProfileFieldComponent implements OnInit, OnDestr
};
if (this.field) {
// Retrieve the handler data.
const handler = this.sitePluginsProvider.getSitePluginHandler(this.field.type || this.field.datatype),
handlerSchema = handler && handler.handlerSchema;
if (handlerSchema) {
// Load first template.
if (handlerSchema.methodTemplates && handlerSchema.methodTemplates.length) {
this.content = handler.handlerSchema.methodTemplates[0].html;
}
// Wait for the instance to be created.
if (this.compileComponent && this.compileComponent.componentObservable &&
handlerSchema.methodJSResult && handlerSchema.methodJSResult.componentInit) {
this.componentObserver = this.compileComponent.componentObservable.subscribe((instance) => {
if (instance) {
// Instance created, call component init.
handlerSchema.methodJSResult.componentInit.apply(instance);
}
});
}
}
this.getHandlerData('profilefield_' + (this.field.type || this.field.datatype));
}
}
/**
* Component destroyed.
*/
ngOnDestroy(): void {
this.componentObserver && this.componentObserver.unsubscribe();
}
}

View File

@ -14,6 +14,7 @@
import { Injectable, Injector } from '@angular/core';
import { Http } from '@angular/http';
import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreLangProvider } from '@providers/lang';
@ -25,6 +26,7 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSitePluginsProvider } from './siteplugins';
import { CoreCompileProvider } from '@core/compile/providers/compile';
import { CoreQuestionProvider } from '@core/question/providers/question';
// Delegates
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
@ -34,15 +36,29 @@ import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delega
import { CoreCourseFormatDelegate } from '@core/course/providers/format-delegate';
import { CoreUserDelegate } from '@core/user/providers/user-delegate';
import { CoreUserProfileFieldDelegate } from '@core/user/providers/user-profile-field-delegate';
import { CoreSettingsDelegate } from '@core/settings/providers/delegate';
import { CoreQuestionDelegate } from '@core/question/providers/delegate';
import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate';
import { AddonMessageOutputDelegate } from '@addon/messageoutput/providers/delegate';
import { AddonModQuizAccessRuleDelegate } from '@addon/mod/quiz/providers/access-rules-delegate';
import { AddonModAssignFeedbackDelegate } from '@addon/mod/assign/providers/feedback-delegate';
import { AddonModAssignSubmissionDelegate } from '@addon/mod/assign/providers/submission-delegate';
// Handler classes.
import { CoreSitePluginsCourseFormatHandler } from '../classes/course-format-handler';
import { CoreSitePluginsCourseOptionHandler } from '../classes/course-option-handler';
import { CoreSitePluginsModuleHandler } from '../classes/module-handler';
import { CoreSitePluginsModulePrefetchHandler } from '../classes/module-prefetch-handler';
import { CoreSitePluginsMainMenuHandler } from '../classes/main-menu-handler';
import { CoreSitePluginsUserProfileHandler } from '../classes/user-handler';
import { CoreSitePluginsUserProfileFieldHandler } from '../classes/user-profile-field-handler';
import { CoreSitePluginsCourseFormatHandler } from '../classes/handlers/course-format-handler';
import { CoreSitePluginsCourseOptionHandler } from '../classes/handlers/course-option-handler';
import { CoreSitePluginsModuleHandler } from '../classes/handlers/module-handler';
import { CoreSitePluginsModulePrefetchHandler } from '../classes/handlers/module-prefetch-handler';
import { CoreSitePluginsMainMenuHandler } from '../classes/handlers/main-menu-handler';
import { CoreSitePluginsUserProfileHandler } from '../classes/handlers/user-handler';
import { CoreSitePluginsUserProfileFieldHandler } from '../classes/handlers/user-profile-field-handler';
import { CoreSitePluginsSettingsHandler } from '../classes/handlers/settings-handler';
import { CoreSitePluginsQuestionHandler } from '../classes/handlers/question-handler';
import { CoreSitePluginsQuestionBehaviourHandler } from '../classes/handlers/question-behaviour-handler';
import { CoreSitePluginsMessageOutputHandler } from '../classes/handlers/message-output-handler';
import { CoreSitePluginsQuizAccessRuleHandler } from '../classes/handlers/quiz-access-rule-handler';
import { CoreSitePluginsAssignFeedbackHandler } from '../classes/handlers/assign-feedback-handler';
import { CoreSitePluginsAssignSubmissionHandler } from '../classes/handlers/assign-submission-handler';
/**
* Helper service to provide functionalities regarding site plugins. It basically has the features to load and register site
@ -64,7 +80,13 @@ export class CoreSitePluginsHelperProvider {
private compileProvider: CoreCompileProvider, private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider,
private courseOptionsDelegate: CoreCourseOptionsDelegate, eventsProvider: CoreEventsProvider,
private courseFormatDelegate: CoreCourseFormatDelegate, private profileFieldDelegate: CoreUserProfileFieldDelegate,
private textUtils: CoreTextUtilsProvider, private filepoolProvider: CoreFilepoolProvider) {
private textUtils: CoreTextUtilsProvider, private filepoolProvider: CoreFilepoolProvider,
private settingsDelegate: CoreSettingsDelegate, private questionDelegate: CoreQuestionDelegate,
private questionBehaviourDelegate: CoreQuestionBehaviourDelegate, private questionProvider: CoreQuestionProvider,
private messageOutputDelegate: AddonMessageOutputDelegate, private accessRulesDelegate: AddonModQuizAccessRuleDelegate,
private assignSubmissionDelegate: AddonModAssignSubmissionDelegate, private translate: TranslateService,
private assignFeedbackDelegate: AddonModAssignFeedbackDelegate) {
this.logger = logger.getInstance('CoreSitePluginsHelperProvider');
// Fetch the plugins on login.
@ -414,7 +436,7 @@ export class CoreSitePluginsHelperProvider {
break;
case 'CoreCourseModuleDelegate':
promise = Promise.resolve(this.registerModuleHandler(plugin, handlerName, handlerSchema, result));
promise = Promise.resolve(this.registerModuleHandler(plugin, handlerName, handlerSchema));
break;
case 'CoreUserDelegate':
@ -426,11 +448,39 @@ export class CoreSitePluginsHelperProvider {
break;
case 'CoreCourseFormatDelegate':
promise = Promise.resolve(this.registerCourseFormatHandler(plugin, handlerName, handlerSchema, result));
promise = Promise.resolve(this.registerCourseFormatHandler(plugin, handlerName, handlerSchema));
break;
case 'CoreUserProfileFieldDelegate':
promise = Promise.resolve(this.registerUserProfileFieldHandler(plugin, handlerName, handlerSchema, result));
promise = Promise.resolve(this.registerUserProfileFieldHandler(plugin, handlerName, handlerSchema));
break;
case 'CoreSettingsDelegate':
promise = Promise.resolve(this.registerSettingsHandler(plugin, handlerName, handlerSchema, result));
break;
case 'CoreQuestionDelegate':
promise = Promise.resolve(this.registerQuestionHandler(plugin, handlerName, handlerSchema));
break;
case 'CoreQuestionBehaviourDelegate':
promise = Promise.resolve(this.registerQuestionBehaviourHandler(plugin, handlerName, handlerSchema));
break;
case 'AddonMessageOutputDelegate':
promise = Promise.resolve(this.registerMessageOutputHandler(plugin, handlerName, handlerSchema, result));
break;
case 'AddonModQuizAccessRuleDelegate':
promise = Promise.resolve(this.registerQuizAccessRuleHandler(plugin, handlerName, handlerSchema));
break;
case 'AddonModAssignFeedbackDelegate':
promise = Promise.resolve(this.registerAssignFeedbackHandler(plugin, handlerName, handlerSchema));
break;
case 'AddonModAssignSubmissionDelegate':
promise = Promise.resolve(this.registerAssignSubmissionHandler(plugin, handlerName, handlerSchema));
break;
default:
@ -454,17 +504,107 @@ export class CoreSitePluginsHelperProvider {
});
}
/**
* Register a handler that relies in a "componentInit" function in a certain delegate.
* These type of handlers will return a generic template and its JS in the main method, so it will be called
* before registering the handler.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @return {string|Promise<string>} A string (or a promise resolved with a string) to identify the handler.
*/
protected registerComponentInitHandler(plugin: any, handlerName: string, handlerSchema: any, delegate: any,
createHandlerFn: (uniqueName: string, result: any) => any): string | Promise<string> {
if (!handlerSchema.method) {
// Required data not provided, stop.
this.logger.warn('Ignore site plugin because it doesn\'t provide method', plugin, handlerSchema);
return;
}
this.logger.debug('Register site plugin', plugin, handlerSchema);
// Execute the main method and its JS. The template returned will be used in the right component.
return this.executeMethodAndJS(plugin, handlerSchema.method).then((result) => {
// Create and register the handler.
const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName),
handler = createHandlerFn(uniqueName, result);
// Store in handlerSchema some data required by the component.
handlerSchema.methodTemplates = result.templates;
handlerSchema.methodJSResult = result.jsResult;
if (result && result.jsResult) {
// Override default handler functions with the result of the method JS.
for (const property in handler) {
if (property != 'constructor' && typeof handler[property] == 'function' &&
typeof result.jsResult[property] == 'function') {
handler[property] = result.jsResult[property].bind(handler);
}
}
}
delegate.registerHandler(handler);
return plugin.component;
}).catch((err) => {
this.logger.error('Error executing main method', plugin.component, handlerSchema.method, err);
});
}
/**
* Given a handler in a plugin, register it in the assign feedback delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @return {string|Promise<string>} A string (or a promise resolved with a string) to identify the handler.
*/
protected registerAssignFeedbackHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise<string> {
return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.assignFeedbackDelegate,
(uniqueName: string, result: any) => {
const type = plugin.component.replace('assignfeedback_', ''),
prefix = this.getPrefixForStrings(plugin.addon);
return new CoreSitePluginsAssignFeedbackHandler(this.translate, uniqueName, type, prefix);
});
}
/**
* Given a handler in a plugin, register it in the assign submission delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @return {string|Promise<string>} A string (or a promise resolved with a string) to identify the handler.
*/
protected registerAssignSubmissionHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise<string> {
return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.assignSubmissionDelegate,
(uniqueName: string, result: any) => {
const type = plugin.component.replace('assignsubmission_', ''),
prefix = this.getPrefixForStrings(plugin.addon);
return new CoreSitePluginsAssignSubmissionHandler(this.translate, uniqueName, type, prefix);
});
}
/**
* Given a handler in a plugin, register it in the course format delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @param {any} initResult Result of the init WS call.
* @return {string} A string to identify the handler.
*/
protected registerCourseFormatHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string {
this.logger.debug('Register site plugin in course format delegate:', plugin, handlerSchema, initResult);
protected registerCourseFormatHandler(plugin: any, handlerName: string, handlerSchema: any): string {
this.logger.debug('Register site plugin in course format delegate:', plugin, handlerSchema);
// Create and register the handler.
const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName),
@ -475,7 +615,7 @@ export class CoreSitePluginsHelperProvider {
}
/**
* Given a handler in an plugin, register it in the course options delegate.
* Given a handler in a plugin, register it in the course options delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
@ -504,7 +644,7 @@ export class CoreSitePluginsHelperProvider {
}
/**
* Given a handler in an plugin, register it in the main menu delegate.
* Given a handler in a plugin, register it in the main menu delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
@ -533,7 +673,7 @@ export class CoreSitePluginsHelperProvider {
}
/**
* Given a handler in an plugin, register it in the module delegate.
* Given a handler in a plugin, register it in the message output delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
@ -541,7 +681,7 @@ export class CoreSitePluginsHelperProvider {
* @param {any} initResult Result of the init WS call.
* @return {string} A string to identify the handler.
*/
protected registerModuleHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string {
protected registerMessageOutputHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string {
if (!handlerSchema.displaydata) {
// Required data not provided, stop.
this.logger.warn('Ignore site plugin because it doesn\'t provide displaydata', plugin, handlerSchema);
@ -549,7 +689,36 @@ export class CoreSitePluginsHelperProvider {
return;
}
this.logger.debug('Register site plugin in module delegate:', plugin, handlerSchema, initResult);
this.logger.debug('Register site plugin in message output delegate:', plugin, handlerSchema, initResult);
// Create and register the handler.
const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName),
prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title),
processorName = plugin.component.replace('message_', '');
this.messageOutputDelegate.registerHandler(new CoreSitePluginsMessageOutputHandler(uniqueName, processorName,
prefixedTitle, plugin, handlerSchema, initResult));
return uniqueName;
}
/**
* Given a handler in a plugin, register it in the module delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @return {string} A string to identify the handler.
*/
protected registerModuleHandler(plugin: any, handlerName: string, handlerSchema: any): string {
if (!handlerSchema.displaydata) {
// Required data not provided, stop.
this.logger.warn('Ignore site plugin because it doesn\'t provide displaydata', plugin, handlerSchema);
return;
}
this.logger.debug('Register site plugin in module delegate:', plugin, handlerSchema);
// Create and register the handler.
const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName),
@ -567,7 +736,89 @@ export class CoreSitePluginsHelperProvider {
}
/**
* Given a handler in an plugin, register it in the user profile delegate.
* Given a handler in a plugin, register it in the question delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @return {string|Promise<string>} A string (or a promise resolved with a string) to identify the handler.
*/
protected registerQuestionHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise<string> {
return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.questionDelegate,
(uniqueName: string, result: any) => {
return new CoreSitePluginsQuestionHandler(uniqueName, plugin.component);
});
}
/**
* Given a handler in a plugin, register it in the question behaviour delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @return {string|Promise<string>} A string (or a promise resolved with a string) to identify the handler.
*/
protected registerQuestionBehaviourHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise<string> {
return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.questionBehaviourDelegate,
(uniqueName: string, result: any) => {
const type = plugin.component.replace('qbehaviour_', '');
return new CoreSitePluginsQuestionBehaviourHandler(this.questionProvider, uniqueName, type, result.templates.length);
});
}
/**
* Given a handler in a plugin, register it in the quiz access rule delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @return {string|Promise<string>} A string (or a promise resolved with a string) to identify the handler.
*/
protected registerQuizAccessRuleHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise<string> {
return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.accessRulesDelegate,
(uniqueName: string, result: any) => {
return new CoreSitePluginsQuizAccessRuleHandler(uniqueName, plugin.component, result.templates.length);
});
}
/**
* Given a handler in a plugin, register it in the settings delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @param {any} initResult Result of the init WS call.
* @return {string} A string to identify the handler.
*/
protected registerSettingsHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string {
if (!handlerSchema.displaydata) {
// Required data not provided, stop.
this.logger.warn('Ignore site plugin because it doesn\'t provide displaydata', plugin, handlerSchema);
return;
}
this.logger.debug('Register site plugin in settings delegate:', plugin, handlerSchema, initResult);
// Create and register the handler.
const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName),
prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title);
this.settingsDelegate.registerHandler(
new CoreSitePluginsSettingsHandler(uniqueName, prefixedTitle, plugin, handlerSchema, initResult));
return uniqueName;
}
/**
* Given a handler in a plugin, register it in the user profile delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
@ -596,51 +847,21 @@ export class CoreSitePluginsHelperProvider {
}
/**
* Given a handler in an plugin, register it in the user profile field delegate.
* Given a handler in a plugin, register it in the user profile field delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @param {any} initResult Result of the init WS call.
* @return {string|Promise<string>} A string (or a promise resolved with a string) to identify the handler.
*/
protected registerUserProfileFieldHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any)
: string | Promise<string> {
if (!handlerSchema.method) {
// Required data not provided, stop.
this.logger.warn('Ignore site plugin because it doesn\'t provide method', plugin, handlerSchema);
protected registerUserProfileFieldHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise<string> {
return;
}
return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.profileFieldDelegate,
(uniqueName: string, result: any) => {
this.logger.debug('Register site plugin in user profile field delegate:', plugin, handlerSchema, initResult);
const fieldType = plugin.component.replace('profilefield_', '');
// Execute the main method and its JS. The template returned will be used in the profile field component.
return this.executeMethodAndJS(plugin, handlerSchema.method).then((result) => {
// Create and register the handler.
const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName),
fieldType = plugin.component.replace('profilefield_', ''),
fieldHandler = new CoreSitePluginsUserProfileFieldHandler(uniqueName, fieldType);
// Store in handlerSchema some data required by the component.
handlerSchema.methodTemplates = result.templates;
handlerSchema.methodJSResult = result.jsResult;
if (result && result.jsResult) {
// Override default handler functions with the result of the method JS.
for (const property in fieldHandler) {
if (property != 'constructor' && typeof fieldHandler[property] == 'function' &&
typeof result.jsResult[property] == 'function') {
fieldHandler[property] = result.jsResult[property].bind(fieldHandler);
}
}
}
this.profileFieldDelegate.registerHandler(fieldHandler);
return fieldType;
}).catch((err) => {
this.logger.error('Error executing main method', handlerSchema.method, err);
return new CoreSitePluginsUserProfileFieldHandler(uniqueName, fieldType);
});
}
}

View File

@ -184,9 +184,9 @@ export class CoreCronDelegate {
return this.setHandlerLastExecutionTime(name, Date.now()).then(() => {
this.scheduleNextExecution(name);
});
}, () => {
}, (error) => {
// Handler call failed. Retry soon.
this.logger.debug(`Execution of handler '${name}' failed.`);
this.logger.error(`Execution of handler '${name}' failed.`, error);
this.scheduleNextExecution(name, CoreCronDelegate.MIN_INTERVAL);
return Promise.reject(null);
@ -440,7 +440,9 @@ export class CoreCronDelegate {
this.handlers[name].timeout = setTimeout(() => {
delete this.handlers[name].timeout;
this.checkAndExecuteHandler(name);
this.checkAndExecuteHandler(name).catch(() => {
// Ignore errors.
});
}, nextExecution);
});
}

View File

@ -918,7 +918,9 @@ export class CoreFileProvider {
* @return {Promise<any>} Promise resolved when done.
*/
clearTmpFolder(): Promise<any> {
return this.removeDir(CoreFileProvider.TMPFOLDER);
return this.removeDir(CoreFileProvider.TMPFOLDER).catch(() => {
// Ignore errors because the folder might not exist.
});
}
/**

View File

@ -975,6 +975,8 @@ export class CoreSitesProvider {
this.logger.debug(`Restore session in site ${siteId}`);
return this.loadSite(siteId);
}).catch(() => {
// No current session.
});
}