@@ -24,12 +24,15 @@ import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main
2424import { CoreH5P } from '@core/h5p/providers/h5p' ;
2525import { CoreH5PDisplayOptions } from '@core/h5p/classes/core' ;
2626import { CoreH5PHelper } from '@core/h5p/classes/helper' ;
27+ import { CoreXAPI } from '@core/xapi/providers/xapi' ;
28+ import { CoreXAPIOffline } from '@core/xapi/providers/offline' ;
2729import { CoreConstants } from '@core/constants' ;
2830import { CoreSite } from '@classes/site' ;
2931
3032import {
3133 AddonModH5PActivity , AddonModH5PActivityProvider , AddonModH5PActivityData , AddonModH5PActivityAccessInfo
3234} from '../../providers/h5pactivity' ;
35+ import { AddonModH5PActivitySyncProvider , AddonModH5PActivitySync } from '../../providers/sync' ;
3336
3437/**
3538 * Component that displays an H5P activity entry page.
@@ -57,17 +60,26 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
5760 fileUrl : string ; // The fileUrl to use to play the package.
5861 state : string ; // State of the file.
5962 siteCanDownload : boolean ;
63+ trackComponent : string ; // Component for tracking.
64+ hasOffline : boolean ;
65+ isOpeningPage : boolean ;
6066
6167 protected fetchContentDefaultError = 'addon.mod_h5pactivity.errorgetactivity' ;
68+ protected syncEventName = AddonModH5PActivitySyncProvider . AUTO_SYNCED ;
6269 protected site : CoreSite ;
6370 protected observer ;
71+ protected messageListenerFunction : ( event : MessageEvent ) => Promise < void > ;
6472
6573 constructor ( injector : Injector ,
6674 @Optional ( ) protected content : Content ) {
6775 super ( injector , content ) ;
6876
6977 this . site = this . sitesProvider . getCurrentSite ( ) ;
7078 this . siteCanDownload = this . site . canDownloadFiles ( ) && ! CoreH5P . instance . isOfflineDisabledInSite ( ) ;
79+
80+ // Listen for messages from the iframe.
81+ this . messageListenerFunction = this . onIframeMessage . bind ( this ) ;
82+ window . addEventListener ( 'message' , this . messageListenerFunction ) ;
7183 }
7284
7385 /**
@@ -96,23 +108,30 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
96108 */
97109 protected async fetchContent ( refresh : boolean = false , sync : boolean = false , showErrors : boolean = false ) : Promise < void > {
98110 try {
99- this . h5pActivity = await AddonModH5PActivity . instance . getH5PActivity ( this . courseId , this . module . id ) ;
111+ this . h5pActivity = await AddonModH5PActivity . instance . getH5PActivity ( this . courseId , this . module . id , false , this . siteId ) ;
100112
101113 this . dataRetrieved . emit ( this . h5pActivity ) ;
102114 this . description = this . h5pActivity . intro ;
103115 this . displayOptions = CoreH5PHelper . decodeDisplayOptions ( this . h5pActivity . displayoptions ) ;
104116
105- if ( this . h5pActivity . package && this . h5pActivity . package [ 0 ] ) {
106- // The online player should use the original file, not the trusted one.
107- this . onlinePlayerUrl = CoreH5P . instance . h5pPlayer . calculateOnlinePlayerUrl (
108- this . site . getURL ( ) , this . h5pActivity . package [ 0 ] . fileurl , this . displayOptions ) ;
117+ if ( sync ) {
118+ await this . syncActivity ( showErrors ) ;
109119 }
110120
111121 await Promise . all ( [
122+ this . checkHasOffline ( ) ,
112123 this . fetchAccessInfo ( ) ,
113124 this . fetchDeployedFileData ( ) ,
114125 ] ) ;
115126
127+ this . trackComponent = this . accessInfo . cansubmit ? AddonModH5PActivityProvider . TRACK_COMPONENT : '' ;
128+
129+ if ( this . h5pActivity . package && this . h5pActivity . package [ 0 ] ) {
130+ // The online player should use the original file, not the trusted one.
131+ this . onlinePlayerUrl = CoreH5P . instance . h5pPlayer . calculateOnlinePlayerUrl (
132+ this . site . getURL ( ) , this . h5pActivity . package [ 0 ] . fileurl , this . displayOptions , this . trackComponent ) ;
133+ }
134+
116135 if ( ! this . siteCanDownload || this . state == CoreConstants . DOWNLOADED ) {
117136 // Cannot download the file or already downloaded, play the package directly.
118137 this . play ( ) ;
@@ -127,13 +146,22 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
127146 }
128147 }
129148
149+ /**
150+ * Fetch the access info and store it in the right variables.
151+ *
152+ * @return Promise resolved when done.
153+ */
154+ protected async checkHasOffline ( ) : Promise < void > {
155+ this . hasOffline = await CoreXAPIOffline . instance . contextHasStatements ( this . h5pActivity . context , this . siteId ) ;
156+ }
157+
130158 /**
131159 * Fetch the access info and store it in the right variables.
132160 *
133161 * @return Promise resolved when done.
134162 */
135163 protected async fetchAccessInfo ( ) : Promise < void > {
136- this . accessInfo = await AddonModH5PActivity . instance . getAccessInformation ( this . h5pActivity . id ) ;
164+ this . accessInfo = await AddonModH5PActivity . instance . getAccessInformation ( this . h5pActivity . id , false , this . siteId ) ;
137165 }
138166
139167 /**
@@ -322,14 +350,121 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
322350 /**
323351 * Go to view user events.
324352 */
325- viewMyAttempts ( ) : void {
326- this . navCtrl . push ( 'AddonModH5PActivityUserAttemptsPage' , { courseId : this . courseId , h5pActivityId : this . h5pActivity . id } ) ;
353+ async viewMyAttempts ( ) : Promise < void > {
354+ this . isOpeningPage = true ;
355+
356+ try {
357+ await this . navCtrl . push ( 'AddonModH5PActivityUserAttemptsPage' , {
358+ courseId : this . courseId ,
359+ h5pActivityId : this . h5pActivity . id ,
360+ } ) ;
361+ } finally {
362+ this . isOpeningPage = false ;
363+ }
364+ }
365+
366+ /**
367+ * Treat an iframe message event.
368+ *
369+ * @param event Event.
370+ * @return Promise resolved when done.
371+ */
372+ protected async onIframeMessage ( event : MessageEvent ) : Promise < void > {
373+ if ( ! event . data || ! CoreXAPI . instance . canPostStatementsInSite ( this . site ) || ! this . isCurrentXAPIPost ( event . data ) ) {
374+ return ;
375+ }
376+
377+ try {
378+ const options = {
379+ offline : this . hasOffline ,
380+ courseId : this . courseId ,
381+ extra : this . h5pActivity . name ,
382+ siteId : this . site . getId ( ) ,
383+ } ;
384+
385+ const sent = await CoreXAPI . instance . postStatements ( this . h5pActivity . context , event . data . component ,
386+ JSON . stringify ( event . data . statements ) , options ) ;
387+
388+ this . hasOffline = ! sent ;
389+
390+ if ( sent ) {
391+ try {
392+ // Invalidate attempts.
393+ await AddonModH5PActivity . instance . invalidateUserAttempts ( this . h5pActivity . id , undefined , this . siteId ) ;
394+ } catch ( error ) {
395+ // Ignore errors.
396+ }
397+ }
398+ } catch ( error ) {
399+ CoreDomUtils . instance . showErrorModalDefault ( error , 'Error sending tracking data.' ) ;
400+ }
401+ }
402+
403+ /**
404+ * Check if an event is an XAPI post statement of the current activity.
405+ *
406+ * @param data Event data.
407+ * @return Whether it's an XAPI post statement of the current activity.
408+ */
409+ protected isCurrentXAPIPost ( data : any ) : boolean {
410+ if ( data . context != 'moodleapp' || data . action != 'xapi_post_statement' || ! data . statements ) {
411+ return false ;
412+ }
413+
414+ // Check the event belongs to this activity.
415+ const trackingUrl = data . statements [ 0 ] && data . statements [ 0 ] . object && data . statements [ 0 ] . object . id ;
416+ if ( ! trackingUrl ) {
417+ return false ;
418+ }
419+
420+ if ( ! this . site . containsUrl ( trackingUrl ) ) {
421+ // The event belongs to another site, weird scenario. Maybe some JS running in background.
422+ return false ;
423+ }
424+
425+ const match = trackingUrl . match ( / x a p i \/ a c t i v i t y \/ ( \d + ) / ) ;
426+
427+ return match && match [ 1 ] == this . h5pActivity . context ;
428+ }
429+
430+ /**
431+ * Performs the sync of the activity.
432+ *
433+ * @return Promise resolved when done.
434+ */
435+ protected sync ( ) : Promise < any > {
436+ return AddonModH5PActivitySync . instance . syncActivity ( this . h5pActivity . context , this . site . getId ( ) ) ;
437+ }
438+
439+ /**
440+ * An autosync event has been received.
441+ *
442+ * @param syncEventData Data receiven on sync observer.
443+ */
444+ protected autoSyncEventReceived ( syncEventData : any ) : void {
445+ this . checkHasOffline ( ) ;
446+ }
447+
448+ /**
449+ * Go to blog posts.
450+ *
451+ * @param event Event.
452+ */
453+ async gotoBlog ( event : any ) : Promise < void > {
454+ this . isOpeningPage = true ;
455+
456+ try {
457+ await super . gotoBlog ( event ) ;
458+ } finally {
459+ this . isOpeningPage = false ;
460+ }
327461 }
328462
329463 /**
330464 * Component destroyed.
331465 */
332466 ngOnDestroy ( ) : void {
333467 this . observer && this . observer . off ( ) ;
468+ window . removeEventListener ( 'message' , this . messageListenerFunction ) ;
334469 }
335470}
0 commit comments