MOBILE-3412 h5pactivity: Support tracking in online
parent
3297ec0c87
commit
90dfe5a891
|
@ -684,6 +684,7 @@
|
|||
"addon.mod_h5pactivity.no_compatible_track": "h5pactivity",
|
||||
"addon.mod_h5pactivity.offlinedisabledwarning": "local_moodlemobileapp",
|
||||
"addon.mod_h5pactivity.outcome": "h5pactivity",
|
||||
"addon.mod_h5pactivity.previewmode": "h5pactivity",
|
||||
"addon.mod_h5pactivity.result_fill-in": "h5pactivity",
|
||||
"addon.mod_h5pactivity.result_other": "h5pactivity",
|
||||
"addon.mod_h5pactivity.review_my_attempts": "h5pactivity",
|
||||
|
|
|
@ -21,6 +21,11 @@
|
|||
<ion-icon name="warning"></ion-icon> {{ 'core.h5p.offlinedisabled' | translate }} {{ 'addon.mod_h5pactivity.offlinedisabledwarning' | translate }}
|
||||
</ion-card>
|
||||
|
||||
<!-- Preview mode. -->
|
||||
<ion-card class="core-warning-card" icon-start *ngIf="accessInfo && !trackComponent">
|
||||
<ion-icon name="warning"></ion-icon> {{ 'addon.mod_h5pactivity.previewmode' | translate }}
|
||||
</ion-card>
|
||||
|
||||
<ion-list *ngIf="deployedFile && !playing">
|
||||
<ion-item text-wrap *ngIf="stateMessage">
|
||||
<p >{{ stateMessage | translate }}</p>
|
||||
|
@ -39,5 +44,5 @@
|
|||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<core-h5p-iframe *ngIf="playing" [fileUrl]="fileUrl" [displayOptions]="displayOptions" [onlinePlayerUrl]="onlinePlayerUrl"></core-h5p-iframe>
|
||||
<core-h5p-iframe *ngIf="playing" [fileUrl]="fileUrl" [displayOptions]="displayOptions" [onlinePlayerUrl]="onlinePlayerUrl" [trackComponent]="trackComponent" [contextId]="h5pActivity.context"></core-h5p-iframe>
|
||||
</core-loading>
|
||||
|
|
|
@ -24,6 +24,7 @@ import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main
|
|||
import { CoreH5P } from '@core/h5p/providers/h5p';
|
||||
import { CoreH5PDisplayOptions } from '@core/h5p/classes/core';
|
||||
import { CoreH5PHelper } from '@core/h5p/classes/helper';
|
||||
import { CoreXAPI } from '@core/xapi/providers/xapi';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreSite } from '@classes/site';
|
||||
|
||||
|
@ -57,10 +58,12 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
|
|||
fileUrl: string; // The fileUrl to use to play the package.
|
||||
state: string; // State of the file.
|
||||
siteCanDownload: boolean;
|
||||
trackComponent: string; // Component for tracking.
|
||||
|
||||
protected fetchContentDefaultError = 'addon.mod_h5pactivity.errorgetactivity';
|
||||
protected site: CoreSite;
|
||||
protected observer;
|
||||
protected messageListenerFunction: (event: MessageEvent) => Promise<void>;
|
||||
|
||||
constructor(injector: Injector,
|
||||
@Optional() protected content: Content) {
|
||||
|
@ -68,6 +71,10 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
|
|||
|
||||
this.site = this.sitesProvider.getCurrentSite();
|
||||
this.siteCanDownload = this.site.canDownloadFiles() && !CoreH5P.instance.isOfflineDisabledInSite();
|
||||
|
||||
// Listen for messages from the iframe.
|
||||
this.messageListenerFunction = this.onIframeMessage.bind(this);
|
||||
window.addEventListener('message', this.messageListenerFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,17 +109,19 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
|
|||
this.description = this.h5pActivity.intro;
|
||||
this.displayOptions = CoreH5PHelper.decodeDisplayOptions(this.h5pActivity.displayoptions);
|
||||
|
||||
if (this.h5pActivity.package && this.h5pActivity.package[0]) {
|
||||
// The online player should use the original file, not the trusted one.
|
||||
this.onlinePlayerUrl = CoreH5P.instance.h5pPlayer.calculateOnlinePlayerUrl(
|
||||
this.site.getURL(), this.h5pActivity.package[0].fileurl, this.displayOptions);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
this.fetchAccessInfo(),
|
||||
this.fetchDeployedFileData(),
|
||||
]);
|
||||
|
||||
this.trackComponent = this.accessInfo.cansubmit ? AddonModH5PActivityProvider.TRACK_COMPONENT : '';
|
||||
|
||||
if (this.h5pActivity.package && this.h5pActivity.package[0]) {
|
||||
// The online player should use the original file, not the trusted one.
|
||||
this.onlinePlayerUrl = CoreH5P.instance.h5pPlayer.calculateOnlinePlayerUrl(
|
||||
this.site.getURL(), this.h5pActivity.package[0].fileurl, this.displayOptions, this.trackComponent);
|
||||
}
|
||||
|
||||
if (!this.siteCanDownload || this.state == CoreConstants.DOWNLOADED) {
|
||||
// Cannot download the file or already downloaded, play the package directly.
|
||||
this.play();
|
||||
|
@ -326,10 +335,56 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
|
|||
this.navCtrl.push('AddonModH5PActivityUserAttemptsPage', {courseId: this.courseId, h5pActivityId: this.h5pActivity.id});
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat an iframe message event.
|
||||
*
|
||||
* @param event Event.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async onIframeMessage(event: MessageEvent): Promise<void> {
|
||||
if (!event.data || !CoreXAPI.instance.canPostStatementInSite(this.site) || !this.isCurrentXAPIPost(event.data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await CoreXAPI.instance.postStatement(event.data.component, JSON.stringify(event.data.statements));
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'Error sending tracking data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an event is an XAPI post statement of the current activity.
|
||||
*
|
||||
* @param data Event data.
|
||||
* @return Whether it's an XAPI post statement of the current activity.
|
||||
*/
|
||||
protected isCurrentXAPIPost(data: any): boolean {
|
||||
if (data.context != 'moodleapp' || data.action != 'xapi_post_statement' || !data.statements) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the event belongs to this activity.
|
||||
const trackingUrl = data.statements[0] && data.statements[0].object && data.statements[0].object.id;
|
||||
if (!trackingUrl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.site.containsUrl(trackingUrl)) {
|
||||
// The event belongs to another site, weird scenario. Maybe some JS running in background.
|
||||
return false;
|
||||
}
|
||||
|
||||
const match = trackingUrl.match(/xapi\/activity\/(\d+)/);
|
||||
|
||||
return match && match[1] == this.h5pActivity.context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.observer && this.observer.off();
|
||||
window.removeEventListener('message', this.messageListenerFunction);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"no_compatible_track": "This interaction ({{$a}}) does not provide tracking information or the tracking provided is not compatible with the current activity version.",
|
||||
"offlinedisabledwarning": "You will need to be online to view the H5P package.",
|
||||
"outcome": "Outcome",
|
||||
"previewmode": "This content is displayed in preview mode. No attempt tracking will be stored.",
|
||||
"result_fill-in": "Fill-in text",
|
||||
"result_other": "Unkown interaction type",
|
||||
"review_my_attempts": "View my attempts",
|
||||
|
|
|
@ -14,9 +14,12 @@
|
|||
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { IonicPage, NavParams } from 'ionic-angular';
|
||||
import { CoreDomUtils } from '@providers/utils/dom';
|
||||
import { AddonModH5PActivityIndexComponent } from '../../components/index/index';
|
||||
import { AddonModH5PActivityData } from '../../providers/h5pactivity';
|
||||
|
||||
import { Translate } from '@singletons/core.singletons';
|
||||
|
||||
/**
|
||||
* Page that displays an H5P activity.
|
||||
*/
|
||||
|
@ -46,4 +49,17 @@ export class AddonModH5PActivityIndexPage {
|
|||
updateData(h5p: AddonModH5PActivityData): void {
|
||||
this.title = h5p.name || this.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can leave the page or not.
|
||||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): Promise<void> {
|
||||
if (!this.h5pComponent.playing) {
|
||||
return;
|
||||
}
|
||||
|
||||
return CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.confirmleaveunknownchanges'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import { makeSingleton, Translate } from '@singletons/core.singletons';
|
|||
@Injectable()
|
||||
export class AddonModH5PActivityProvider {
|
||||
static COMPONENT = 'mmaModH5PActivity';
|
||||
static TRACK_COMPONENT = 'mod_h5pactivity'; // Component for tracking.
|
||||
|
||||
protected ROOT_CACHE_KEY = 'mmaModH5PActivity:';
|
||||
|
||||
|
@ -595,6 +596,7 @@ export type AddonModH5PActivityData = {
|
|||
grademethod: number; // Which H5P attempt is used for grading.
|
||||
contenthash?: string; // Sha1 hash of file content.
|
||||
coursemodule: number; // Coursemodule.
|
||||
context: number; // Context ID.
|
||||
introfiles: CoreWSExternalFile[];
|
||||
package: CoreWSExternalFile[];
|
||||
deployedfile?: {
|
||||
|
|
|
@ -88,6 +88,7 @@ import { CoreFilterModule } from '@core/filter/filter.module';
|
|||
import { CoreH5PModule } from '@core/h5p/h5p.module';
|
||||
import { CoreSearchModule } from '@core/search/search.module';
|
||||
import { CoreEditorModule } from '@core/editor/editor.module';
|
||||
import { CoreXAPIModule } from '@core/xapi/xapi.module';
|
||||
|
||||
// Addon modules.
|
||||
import { AddonBadgesModule } from '@addon/badges/badges.module';
|
||||
|
@ -241,6 +242,7 @@ export const WP_PROVIDER: any = null;
|
|||
CoreH5PModule,
|
||||
CoreSearchModule,
|
||||
CoreEditorModule,
|
||||
CoreXAPIModule,
|
||||
AddonBadgesModule,
|
||||
AddonBlogModule,
|
||||
AddonCalendarModule,
|
||||
|
|
|
@ -684,6 +684,7 @@
|
|||
"addon.mod_h5pactivity.no_compatible_track": "This interaction ({{$a}}) does not provide tracking information or the tracking provided is not compatible with the current activity version.",
|
||||
"addon.mod_h5pactivity.offlinedisabledwarning": "You will need to be online to view the H5P package.",
|
||||
"addon.mod_h5pactivity.outcome": "Outcome",
|
||||
"addon.mod_h5pactivity.previewmode": "This content is displayed in preview mode. No attempt tracking will be stored.",
|
||||
"addon.mod_h5pactivity.result_fill-in": "Fill-in text",
|
||||
"addon.mod_h5pactivity.result_other": "Unkown interaction type",
|
||||
"addon.mod_h5pactivity.review_my_attempts": "View my attempts",
|
||||
|
@ -1407,6 +1408,7 @@
|
|||
"core.confirmdeletefile": "Are you sure you want to delete this file?",
|
||||
"core.confirmgotabroot": "Are you sure you want to go back to {{name}}?",
|
||||
"core.confirmgotabrootdefault": "Are you sure you want to go to the initial page of the current tab?",
|
||||
"core.confirmleaveunknownchanges": "Are you sure you want to leave this page? If you have some unsaved changes they will be lost.",
|
||||
"core.confirmloss": "Are you sure? All changes will be lost.",
|
||||
"core.confirmopeninbrowser": "Do you want to open it in a web browser?",
|
||||
"core.considereddigitalminor": "You are too young to create an account on this site.",
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* Handle display options included in the URL and put them in the H5PIntegration object if it exists.
|
||||
*/
|
||||
|
||||
if (window.H5PIntegration && window.H5PIntegration.contents && location.search) {
|
||||
var contentData = window.H5PIntegration.contents[Object.keys(window.H5PIntegration.contents)[0]];
|
||||
|
||||
if (contentData) {
|
||||
contentData.displayOptions = contentData.displayOptions || {};
|
||||
|
||||
var search = location.search.replace(/^\?/, ''),
|
||||
split = search.split('&');
|
||||
|
||||
split.forEach(function(param) {
|
||||
var nameAndValue = param.split('=');
|
||||
if (nameAndValue.length == 2) {
|
||||
contentData.displayOptions[nameAndValue[0]] = nameAndValue[1] === '1' || nameAndValue[1] === 'true';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -71,6 +71,21 @@ H5PEmbedCommunicator = (function() {
|
|||
// Parent origin can be anything.
|
||||
window.parent.postMessage(data, '*');
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a xAPI statement to LMS.
|
||||
*
|
||||
* @param {string} component
|
||||
* @param {Object} statements
|
||||
*/
|
||||
self.post = function(component, statements) {
|
||||
window.parent.postMessage({
|
||||
context: 'moodleapp',
|
||||
action: 'xapi_post_statement',
|
||||
component: component,
|
||||
statements: statements,
|
||||
}, '*');
|
||||
};
|
||||
}
|
||||
|
||||
return (window.postMessage && window.addEventListener ? new Communicator() : undefined);
|
||||
|
@ -150,6 +165,38 @@ document.onreadystatechange = function() {
|
|||
}, 0);
|
||||
});
|
||||
|
||||
// Get emitted xAPI data.
|
||||
H5P.externalDispatcher.on('xAPI', function(event) {
|
||||
var moodlecomponent = H5P.getMoodleComponent();
|
||||
if (moodlecomponent == undefined) {
|
||||
return;
|
||||
}
|
||||
// Skip malformed events.
|
||||
var hasStatement = event && event.data && event.data.statement;
|
||||
if (!hasStatement) {
|
||||
return;
|
||||
}
|
||||
|
||||
var statement = event.data.statement;
|
||||
var validVerb = statement.verb && statement.verb.id;
|
||||
if (!validVerb) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isCompleted = statement.verb.id === 'http://adlnet.gov/expapi/verbs/answered'
|
||||
|| statement.verb.id === 'http://adlnet.gov/expapi/verbs/completed';
|
||||
|
||||
var isChild = statement.context && statement.context.contextActivities &&
|
||||
statement.context.contextActivities.parent &&
|
||||
statement.context.contextActivities.parent[0] &&
|
||||
statement.context.contextActivities.parent[0].id;
|
||||
|
||||
if (isCompleted && !isChild) {
|
||||
var statements = H5P.getXAPIStatements(this.contentId, statement);
|
||||
H5PEmbedCommunicator.post(moodlecomponent, statements);
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger initial resize for instance.
|
||||
H5P.trigger(instance, 'resize');
|
||||
};
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
H5P.findInstanceFromId = function (contentId) {
|
||||
if (!contentId) {
|
||||
return H5P.instances[0];
|
||||
}
|
||||
if (H5P.instances !== undefined) {
|
||||
for (var i = 0; i < H5P.instances.length; i++) {
|
||||
if (H5P.instances[i].contentId === contentId) {
|
||||
return H5P.instances[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
H5P.getXAPIStatements = function (contentId, statement) {
|
||||
var statements = [];
|
||||
var instance = H5P.findInstanceFromId(contentId);
|
||||
if (!instance){
|
||||
return statements;
|
||||
}
|
||||
if (instance.getXAPIData == undefined) {
|
||||
var xAPIData = {
|
||||
statement: statement
|
||||
};
|
||||
} else {
|
||||
var xAPIData = instance.getXAPIData();
|
||||
}
|
||||
if (xAPIData.statement != undefined) {
|
||||
statements.push(xAPIData.statement);
|
||||
}
|
||||
if (xAPIData.children != undefined) {
|
||||
statements = statements.concat(xAPIData.children.map(a => a.statement));
|
||||
}
|
||||
return statements;
|
||||
};
|
||||
H5P.getMoodleComponent = function () {
|
||||
if (H5PIntegration.moodleComponent) {
|
||||
return H5PIntegration.moodleComponent;
|
||||
}
|
||||
return undefined;
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* Handle params included in the URL and put them in the H5PIntegration object if it exists.
|
||||
*/
|
||||
|
||||
if (window.H5PIntegration && window.H5PIntegration.contents && location.search) {
|
||||
var contentData = window.H5PIntegration.contents[Object.keys(window.H5PIntegration.contents)[0]];
|
||||
|
||||
var search = location.search.replace(/^\?/, '');
|
||||
var split = search.split('&');
|
||||
|
||||
split.forEach(function(param) {
|
||||
var nameAndValue = param.split('=');
|
||||
|
||||
if (nameAndValue[0] == 'displayOptions' && contentData) {
|
||||
try {
|
||||
contentData.displayOptions = contentData.displayOptions || {};
|
||||
|
||||
var displayOptions = JSON.parse(decodeURIComponent(nameAndValue[1]));
|
||||
|
||||
if (displayOptions && typeof displayOptions == 'object') {
|
||||
Object.assign(contentData.displayOptions, displayOptions);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing display options', decodeURIComponent(nameAndValue[1]));
|
||||
}
|
||||
} else if (nameAndValue[0] == 'component') {
|
||||
window.H5PIntegration.moodleComponent = nameAndValue[1];
|
||||
} else if (nameAndValue[0] == 'trackingUrl' && contentData) {
|
||||
contentData.url = nameAndValue[1];
|
||||
}
|
||||
});
|
||||
}
|
|
@ -615,6 +615,8 @@ export class CoreH5PCore {
|
|||
urls.push(libUrl + script);
|
||||
});
|
||||
|
||||
urls.push(CoreTextUtils.instance.concatenatePaths(libUrl, 'moodle/js/h5p_overrides.js'));
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import { CoreSites } from '@providers/sites';
|
|||
import { CoreMimetypeUtils } from '@providers/utils/mimetype';
|
||||
import { CoreTextUtils } from '@providers/utils/text';
|
||||
import { CoreUtils } from '@providers/utils/utils';
|
||||
import { CoreUser } from '@core/user/providers/user';
|
||||
import { CoreH5P } from '../providers/h5p';
|
||||
import { CoreH5PCore, CoreH5PDisplayOptions } from './core';
|
||||
import { FileEntry } from '@ionic-native/file';
|
||||
|
@ -90,6 +91,13 @@ export class CoreH5PHelper {
|
|||
static async getCoreSettings(siteId?: string): Promise<any> {
|
||||
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
let user;
|
||||
|
||||
try {
|
||||
user = await CoreUser.instance.getProfile(site.getUserId(), undefined, true);
|
||||
} catch (error) {
|
||||
// Ignore errors.
|
||||
}
|
||||
|
||||
const basePath = CoreFile.instance.getBasePathInstant();
|
||||
const ajaxPaths = {
|
||||
|
@ -110,7 +118,7 @@ export class CoreH5PHelper {
|
|||
l10n: {
|
||||
H5P: CoreH5P.instance.h5pCore.getLocalization(),
|
||||
},
|
||||
user: [],
|
||||
user: {name: site.getInfo().fullname, mail: user && user.email},
|
||||
hubIsEnabled: false,
|
||||
reportingIsEnabled: false,
|
||||
crossorigin: null,
|
||||
|
|
|
@ -17,6 +17,7 @@ import { CoreSites } from '@providers/sites';
|
|||
import { CoreTextUtils } from '@providers/utils/text';
|
||||
import { CoreUrlUtils } from '@providers/utils/url';
|
||||
import { CoreUtils } from '@providers/utils/utils';
|
||||
import { CoreXAPI } from '@core/xapi/providers/xapi';
|
||||
import { CoreH5P } from '../providers/h5p';
|
||||
import { CoreH5PCore, CoreH5PDisplayOptions, CoreH5PContentData, CoreH5PDependenciesFiles } from './core';
|
||||
import { CoreH5PHelper } from './helper';
|
||||
|
@ -81,7 +82,7 @@ export class CoreH5PPlayer {
|
|||
resizeCode: this.getResizeCode(),
|
||||
title: content.slug,
|
||||
displayOptions: {},
|
||||
url: this.getEmbedUrl(site.getURL(), h5pUrl),
|
||||
url: '', // It will be filled using dynamic params if needed.
|
||||
contentUrl: contentUrl,
|
||||
metadata: content.metadata,
|
||||
contentUserData: [
|
||||
|
@ -109,9 +110,9 @@ export class CoreH5PPlayer {
|
|||
html += '<script type="text/javascript">var H5PIntegration = ' +
|
||||
JSON.stringify(result.settings).replace(/\//g, '\\/') + '</script>';
|
||||
|
||||
// Add our own script to handle the display options.
|
||||
// Add our own script to handle the params.
|
||||
html += '<script type="text/javascript" src="' + CoreTextUtils.instance.concatenatePaths(
|
||||
this.h5pCore.h5pFS.getCoreH5PPath(), 'moodle/js/displayoptions.js') + '"></script>';
|
||||
this.h5pCore.h5pFS.getCoreH5PPath(), 'moodle/js/params.js') + '"></script>';
|
||||
|
||||
html += '</head><body>';
|
||||
|
||||
|
@ -241,20 +242,34 @@ export class CoreH5PPlayer {
|
|||
*
|
||||
* @param fileUrl URL of the H5P package.
|
||||
* @param displayOptions Display options.
|
||||
* @param component Component to send xAPI events to.
|
||||
* @param contextId Context ID where the H5P is. Required for tracking.
|
||||
* @param siteId The site ID. If not defined, current site.
|
||||
* @return Promise resolved with the file URL if exists, rejected otherwise.
|
||||
*/
|
||||
async getContentIndexFileUrl(fileUrl: string, displayOptions?: CoreH5PDisplayOptions, siteId?: string): Promise<string> {
|
||||
async getContentIndexFileUrl(fileUrl: string, displayOptions?: CoreH5PDisplayOptions, component?: string, contextId?: number,
|
||||
siteId?: string): Promise<string> {
|
||||
|
||||
siteId = siteId || CoreSites.instance.getCurrentSiteId();
|
||||
|
||||
const path = await this.h5pCore.h5pFS.getContentIndexFileUrl(fileUrl, siteId);
|
||||
|
||||
// Add display options to the URL.
|
||||
// Add display options and component to the URL.
|
||||
const data = await this.h5pCore.h5pFramework.getContentDataByUrl(fileUrl, siteId);
|
||||
|
||||
displayOptions = this.h5pCore.fixDisplayOptions(displayOptions, data.id);
|
||||
|
||||
return CoreUrlUtils.instance.addParamsToUrl(path, displayOptions, undefined, true);
|
||||
const params = {
|
||||
displayOptions: JSON.stringify(displayOptions),
|
||||
component: component || '',
|
||||
trackingUrl: undefined,
|
||||
};
|
||||
|
||||
if (contextId) {
|
||||
params.trackingUrl = await CoreXAPI.instance.getUrl(contextId, 'activity', siteId);
|
||||
}
|
||||
|
||||
return CoreUrlUtils.instance.addParamsToUrl(path, params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,6 +38,8 @@ export class CoreH5PIframeComponent implements OnChanges {
|
|||
@Input() fileUrl?: string; // The URL of the H5P file. If not supplied, onlinePlayerUrl is required.
|
||||
@Input() displayOptions?: CoreH5PDisplayOptions; // Display options.
|
||||
@Input() onlinePlayerUrl?: string; // The URL of the online player to display the H5P package.
|
||||
@Input() trackComponent?: string; // Component to send xAPI events to.
|
||||
@Input() contextId?: number; // Context ID. Required for tracking.
|
||||
@Output() onIframeUrlSet = new EventEmitter<{src: string, online: boolean}>();
|
||||
@Output() onIframeLoaded = new EventEmitter<void>();
|
||||
|
||||
|
@ -93,7 +95,7 @@ export class CoreH5PIframeComponent implements OnChanges {
|
|||
this.iframeSrc = localUrl;
|
||||
} else {
|
||||
this.onlinePlayerUrl = this.onlinePlayerUrl || CoreH5P.instance.h5pPlayer.calculateOnlinePlayerUrl(
|
||||
this.site.getURL(), this.fileUrl, this.displayOptions);
|
||||
this.site.getURL(), this.fileUrl, this.displayOptions, this.trackComponent);
|
||||
|
||||
// Never allow downloading in the app. This will only work if the user is allowed to change the params.
|
||||
const src = this.onlinePlayerUrl.replace(CoreH5PCore.DISPLAY_OPTION_DOWNLOAD + '=1',
|
||||
|
@ -121,7 +123,8 @@ export class CoreH5PIframeComponent implements OnChanges {
|
|||
*/
|
||||
protected async getLocalUrl(): Promise<string> {
|
||||
try {
|
||||
const url = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(this.fileUrl, this.displayOptions, this.siteId);
|
||||
const url = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(this.fileUrl, this.displayOptions,
|
||||
this.trackComponent, this.contextId, this.siteId);
|
||||
|
||||
return url;
|
||||
} catch (error) {
|
||||
|
@ -135,7 +138,7 @@ export class CoreH5PIframeComponent implements OnChanges {
|
|||
|
||||
// File treated. Try to get the index file URL again.
|
||||
const url = await CoreH5P.instance.h5pPlayer.getContentIndexFileUrl(this.fileUrl, this.displayOptions,
|
||||
this.siteId);
|
||||
this.trackComponent, this.contextId, this.siteId);
|
||||
|
||||
return url;
|
||||
} catch (error) {
|
||||
|
|
|
@ -391,7 +391,7 @@ export class CoreH5PProvider {
|
|||
* @param siteId Site ID (empty for current site).
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
async invalidateAvailableInContexts(url: string, siteId?: string): Promise<void> {
|
||||
async invalidateGetTrustedH5PFile(url: string, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
await site.invalidateWsCacheForKey(this.getTrustedH5PFileCacheKey(url));
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreSites } from '@providers/sites';
|
||||
import { CoreTextUtils } from '@providers/utils/text';
|
||||
import { CoreSite } from '@classes/site';
|
||||
|
||||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
|
||||
/**
|
||||
* Service to provide XAPI functionalities.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreXAPIProvider {
|
||||
|
||||
protected ROOT_CACHE_KEY = 'CoreXAPI:';
|
||||
|
||||
/**
|
||||
* Returns whether or not WS to post XAPI statement is available.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with true if ws is available, false otherwise.
|
||||
* @since 3.9
|
||||
*/
|
||||
async canPostStatement(siteId?: string): Promise<boolean> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
return this.canPostStatementInSite(site);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not WS to post XAPI statement is available in a certain site.
|
||||
*
|
||||
* @param site Site. If not defined, current site.
|
||||
* @return Promise resolved with true if ws is available, false otherwise.
|
||||
* @since 3.9
|
||||
*/
|
||||
canPostStatementInSite(site?: CoreSite): boolean {
|
||||
site = site || CoreSites.instance.getCurrentSite();
|
||||
|
||||
return site.wsAvailable('core_xapi_statement_post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL for XAPI events.
|
||||
*
|
||||
* @param contextId Context ID.
|
||||
* @param type Type (e.g. 'activity').
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async getUrl(contextId: number, type: string, siteId?: string): Promise<string> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
return CoreTextUtils.instance.concatenatePaths(site.getURL(), `xapi/${type}/${contextId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post an statement.
|
||||
*
|
||||
* @param component Component.
|
||||
* @param json JSON string to send.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async postStatement(component: string, json: string, siteId?: string): Promise<number[]> {
|
||||
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
const data = {
|
||||
component: component,
|
||||
requestjson: json,
|
||||
};
|
||||
|
||||
return site.write('core_xapi_statement_post', data);
|
||||
}
|
||||
}
|
||||
|
||||
export class CoreXAPI extends makeSingleton(CoreXAPIProvider) {}
|
|
@ -0,0 +1,31 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CoreXAPIProvider } from './providers/xapi';
|
||||
|
||||
// List of providers (without handlers).
|
||||
export const CORE_XAPI_PROVIDERS: any[] = [
|
||||
CoreXAPIProvider,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [],
|
||||
providers: [
|
||||
CoreXAPIProvider,
|
||||
],
|
||||
exports: []
|
||||
})
|
||||
export class CoreXAPIModule { }
|
|
@ -43,6 +43,7 @@
|
|||
"confirmdeletefile": "Are you sure you want to delete this file?",
|
||||
"confirmgotabroot": "Are you sure you want to go back to {{name}}?",
|
||||
"confirmgotabrootdefault": "Are you sure you want to go to the initial page of the current tab?",
|
||||
"confirmleaveunknownchanges": "Are you sure you want to leave this page? If you have some unsaved changes they will be lost.",
|
||||
"confirmloss": "Are you sure? All changes will be lost.",
|
||||
"confirmopeninbrowser": "Do you want to open it in a web browser?",
|
||||
"considereddigitalminor": "You are too young to create an account on this site.",
|
||||
|
|
Loading…
Reference in New Issue