Skip to content

Commit 428e753

Browse files
authored
Merge pull request #2405 from dpalou/MOBILE-3447
Mobile 3447
2 parents badb71c + 55d3af3 commit 428e753

File tree

8 files changed

+415
-394
lines changed

8 files changed

+415
-394
lines changed

package-lock.json

Lines changed: 227 additions & 282 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
"cordova-plugin-file-transfer": "1.7.1",
102102
"cordova-plugin-geolocation": "git+https://github.com/apache/cordova-plugin-geolocation.git#89cf51d222e8f225bdfb661965b3007d669c40ff",
103103
"cordova-plugin-globalization": "1.11.0",
104-
"cordova-plugin-inappbrowser": "3.2.0",
104+
"cordova-plugin-inappbrowser": "git+https://github.com/apache/cordova-plugin-inappbrowser.git#d2b512ed048ce9345e61901b29ba7dd4271a73bf",
105105
"cordova-plugin-ionic-keyboard": "2.1.3",
106106
"cordova-plugin-ionic-webview": "4.1.3",
107107
"cordova-plugin-local-notification": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle",

src/addon/mod/lti/components/index/index.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Component, Optional, Injector } from '@angular/core';
1616
import { Content } from 'ionic-angular';
1717
import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
1818
import { AddonModLtiProvider, AddonModLtiLti } from '../../providers/lti';
19+
import { AddonModLtiHelper } from '../../providers/helper';
1920

2021
/**
2122
* Component that displays an LTI entry page.
@@ -92,18 +93,6 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo
9293
* Launch the LTI.
9394
*/
9495
launch(): void {
95-
this.ltiProvider.getLtiLaunchData(this.lti.id).then((launchData) => {
96-
// "View" LTI.
97-
this.ltiProvider.logView(this.lti.id, this.lti.name).then(() => {
98-
this.checkCompletion();
99-
}).catch((error) => {
100-
// Ignore errors.
101-
});
102-
103-
// Launch LTI.
104-
return this.ltiProvider.launch(launchData.endpoint, launchData.parameters);
105-
}).catch((message) => {
106-
this.domUtils.showErrorModalDefault(message, 'core.error', true);
107-
});
96+
AddonModLtiHelper.instance.getDataAndLaunch(this.courseId, this.module, this.lti);
10897
}
10998
}

src/addon/mod/lti/lti.module.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { NgModule } from '@angular/core';
1616
import { AddonModLtiComponentsModule } from './components/components.module';
1717
import { AddonModLtiModuleHandler } from './providers/module-handler';
1818
import { AddonModLtiProvider } from './providers/lti';
19+
import { AddonModLtiHelperProvider } from './providers/helper';
1920
import { AddonModLtiLinkHandler } from './providers/link-handler';
2021
import { AddonModLtiListLinkHandler } from './providers/list-link-handler';
2122
import { AddonModLtiPrefetchHandler } from './providers/prefetch-handler';
@@ -25,7 +26,8 @@ import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-
2526

2627
// List of providers (without handlers).
2728
export const ADDON_MOD_LTI_PROVIDERS: any[] = [
28-
AddonModLtiProvider
29+
AddonModLtiProvider,
30+
AddonModLtiHelperProvider,
2931
];
3032

3133
@NgModule({
@@ -36,10 +38,11 @@ export const ADDON_MOD_LTI_PROVIDERS: any[] = [
3638
],
3739
providers: [
3840
AddonModLtiProvider,
41+
AddonModLtiHelperProvider,
3942
AddonModLtiModuleHandler,
4043
AddonModLtiLinkHandler,
4144
AddonModLtiListLinkHandler,
42-
AddonModLtiPrefetchHandler
45+
AddonModLtiPrefetchHandler,
4346
]
4447
})
4548
export class AddonModLtiModule {
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { Injectable } from '@angular/core';
16+
import { Platform } from 'ionic-angular';
17+
import { CoreEvents, CoreEventsProvider } from '@providers/events';
18+
import { CoreSites } from '@providers/sites';
19+
import { CoreDomUtils } from '@providers/utils/dom';
20+
import { CoreCourse } from '@core/course/providers/course';
21+
import { AddonModLti, AddonModLtiLti } from './lti';
22+
23+
import { makeSingleton } from '@singletons/core.singletons';
24+
25+
/**
26+
* Service that provides some helper functions for LTI.
27+
*/
28+
@Injectable()
29+
export class AddonModLtiHelperProvider {
30+
31+
protected pendingCheckCompletion: {[moduleId: string]: {courseId: number, module: any}} = {};
32+
33+
constructor(platform: Platform) {
34+
35+
platform.resume.subscribe(() => {
36+
// User went back to the app, check pending completions.
37+
for (const moduleId in this.pendingCheckCompletion) {
38+
const data = this.pendingCheckCompletion[moduleId];
39+
40+
CoreCourse.instance.checkModuleCompletion(data.courseId, data.module.completiondata);
41+
}
42+
});
43+
44+
// Clear pending completion on logout.
45+
CoreEvents.instance.on(CoreEventsProvider.LOGOUT, () => {
46+
this.pendingCheckCompletion = {};
47+
});
48+
}
49+
50+
/**
51+
* Get needed data and launch the LTI.
52+
*
53+
* @param courseId Course ID.
54+
* @param module Module.
55+
* @param lti LTI instance. If not provided it will be obtained.
56+
* @param siteId Site ID. If not defined, current site.
57+
* @return Promise resolved when done.
58+
*/
59+
async getDataAndLaunch(courseId: number, module: any, lti?: AddonModLtiLti, siteId?: string): Promise<void> {
60+
siteId = siteId || CoreSites.instance.getCurrentSiteId();
61+
62+
const modal = CoreDomUtils.instance.showModalLoading();
63+
64+
try {
65+
const openInBrowser = await AddonModLti.instance.isOpenInAppBrowserDisabled(siteId);
66+
67+
if (openInBrowser) {
68+
const site = await CoreSites.instance.getSite(siteId);
69+
70+
// The view event is triggered by the browser, mark the module as pending to check completion.
71+
this.pendingCheckCompletion[module.id] = {
72+
courseId,
73+
module,
74+
};
75+
76+
await site.openInBrowserWithAutoLogin(module.url);
77+
} else {
78+
// Open in app.
79+
if (!lti) {
80+
lti = await AddonModLti.instance.getLti(courseId, module.id);
81+
}
82+
83+
const launchData = await AddonModLti.instance.getLtiLaunchData(lti.id);
84+
85+
// "View" LTI without blocking the UI.
86+
this.logViewAndCheckCompletion(courseId, module, lti.id, lti.name, siteId);
87+
88+
// Launch LTI.
89+
return AddonModLti.instance.launch(launchData.endpoint, launchData.parameters);
90+
}
91+
} catch (error) {
92+
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_lti.errorgetlti', true);
93+
} finally {
94+
modal.dismiss();
95+
}
96+
}
97+
98+
/**
99+
* Report the LTI as being viewed and check completion.
100+
*
101+
* @param courseId Course ID.
102+
* @param module Module.
103+
* @param ltiId LTI id.
104+
* @param name Name of the lti.
105+
* @param siteId Site ID. If not defined, current site.
106+
* @return Promise resolved when done.
107+
*/
108+
async logViewAndCheckCompletion(courseId: number, module: any, ltiId: number, name?: string, siteId?: string): Promise<void> {
109+
try {
110+
await AddonModLti.instance.logView(ltiId, name);
111+
112+
CoreCourse.instance.checkModuleCompletion(courseId, module.completiondata);
113+
} catch (error) {
114+
// Ignore errors.
115+
}
116+
}
117+
}
118+
119+
export class AddonModLtiHelper extends makeSingleton(AddonModLtiHelperProvider) {}

src/addon/mod/lti/providers/lti.ts

Lines changed: 51 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import { Injectable, NgZone } from '@angular/core';
15+
import { Injectable } from '@angular/core';
1616
import { TranslateService } from '@ngx-translate/core';
1717
import { CoreAppProvider } from '@providers/app';
1818
import { CoreFileProvider } from '@providers/file';
@@ -24,6 +24,8 @@ import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
2424
import { CoreSite } from '@classes/site';
2525
import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws';
2626

27+
import { makeSingleton } from '@singletons/core.singletons';
28+
2729
/**
2830
* Service that provides some features for LTI.
2931
*/
@@ -41,8 +43,7 @@ export class AddonModLtiProvider {
4143
private utils: CoreUtilsProvider,
4244
private translate: TranslateService,
4345
private appProvider: CoreAppProvider,
44-
private logHelper: CoreCourseLogHelperProvider,
45-
protected zone: NgZone) {}
46+
private logHelper: CoreCourseLogHelperProvider) {}
4647

4748
/**
4849
* Delete launcher.
@@ -65,10 +66,23 @@ export class AddonModLtiProvider {
6566
return url;
6667
}
6768

68-
// Generate an empty page with the JS code.
69-
const text = '<script type="text/javascript"> \n' +
69+
// Generate a form with the params.
70+
let text = '<form action="' + url + '" name="ltiLaunchForm" ' +
71+
'method="post" encType="application/x-www-form-urlencoded">\n';
72+
params.forEach((p) => {
73+
if (p.name == 'ext_submit') {
74+
text += ' <input type="submit"';
75+
} else {
76+
text += ' <input type="hidden" name="' + this.textUtils.escapeHTML(p.name) + '"';
77+
}
78+
text += ' value="' + this.textUtils.escapeHTML(p.value) + '"/>\n';
79+
});
80+
text += '</form>\n';
81+
82+
// Add an in-line script to automatically submit the form.
83+
text += '<script type="text/javascript"> \n' +
7084
' window.onload = function() { \n' +
71-
this.getLaunchJSCode(url, params) +
85+
' document.ltiLaunchForm.submit(); \n' +
7286
' }; \n' +
7387
'</script> \n';
7488

@@ -81,42 +95,6 @@ export class AddonModLtiProvider {
8195
}
8296
}
8397

84-
/**
85-
* Get the Javascript code to launch the LTI tool.
86-
*
87-
* @param url Launch URL.
88-
* @param params Launch params.
89-
* @return Javascript code.
90-
*/
91-
getLaunchJSCode(url: string, params: AddonModLtiParam[]): string {
92-
// Create the form.
93-
let jsCode = 'var form = document.createElement("form");\n' +
94-
'form.method = "post";\n' +
95-
'form.setAttribute("encType", "application/x-www-form-urlencoded");\n' +
96-
`form.setAttribute("action", "${url}");\n`;
97-
98-
// Create the inputs based on the params.
99-
params.forEach((p) => {
100-
jsCode += 'var input = document.createElement("input");\n';
101-
102-
if (p.name == 'ext_submit') {
103-
jsCode += 'input.type = "submit";\n';
104-
} else {
105-
jsCode += 'input.type = "hidden";\n' +
106-
'input.name = "' + this.textUtils.escapeHTML(p.name) + '";\n';
107-
}
108-
109-
jsCode += 'input.value = "' + this.textUtils.escapeHTML(p.value) + '";\n' +
110-
'form.appendChild(input);\n';
111-
});
112-
113-
// Add the form to the document and submit it.
114-
jsCode += 'document.body.appendChild(form);\n' +
115-
'form.submit();\n';
116-
117-
return jsCode;
118-
}
119-
12098
/**
12199
* Get a LTI.
122100
*
@@ -217,6 +195,30 @@ export class AddonModLtiProvider {
217195
return this.sitesProvider.getCurrentSite().invalidateWsCacheForKey(this.getLtiLaunchDataCacheKey(id));
218196
}
219197

198+
/**
199+
* Check if open in InAppBrowser is disabled.
200+
*
201+
* @param siteId Site ID. If not defined, current site.
202+
* @return Promise resolved with boolean: whether it's disabled.
203+
*/
204+
async isOpenInAppBrowserDisabled(siteId?: string): Promise<boolean> {
205+
const site = await this.sitesProvider.getSite(siteId);
206+
207+
return this.isOpenInAppBrowserDisabledInSite(site);
208+
}
209+
210+
/**
211+
* Check if open in InAppBrowser is disabled.
212+
*
213+
* @param site Site. If not defined, current site.
214+
* @return Whether it's disabled.
215+
*/
216+
isOpenInAppBrowserDisabledInSite(site?: CoreSite): boolean {
217+
site = site || this.sitesProvider.getCurrentSite();
218+
219+
return site.isFeatureDisabled('CoreCourseModuleDelegate_AddonModLti:openInAppBrowser');
220+
}
221+
220222
/**
221223
* Launch LTI.
222224
*
@@ -229,40 +231,13 @@ export class AddonModLtiProvider {
229231
throw this.translate.instant('addon.mod_lti.errorinvalidlaunchurl');
230232
}
231233

232-
if (this.appProvider.isMobile()) {
233-
// Open it in InAppBrowser. Use JS code because IAB has a bug in iOS when opening local files.
234-
const jsCode = this.getLaunchJSCode(url, params);
235-
236-
const iabInstance = this.utils.openInApp('about:blank');
237-
238-
// Execute the JS code when the page is loaded.
239-
let codeExecuted = false;
240-
const executeCode = (): void => {
241-
if (codeExecuted) {
242-
return;
243-
}
244-
245-
codeExecuted = true;
246-
loadStopSubscription && loadStopSubscription.unsubscribe();
234+
// Generate launcher and open it.
235+
const launcherUrl = await this.generateLauncher(url, params);
247236

248-
// Execute the callback in the Angular zone, so change detection doesn't stop working.
249-
this.zone.run(() => {
250-
iabInstance.executeScript({code: jsCode});
251-
});
252-
};
253-
254-
const loadStopSubscription = iabInstance.on('loadstop').subscribe((event) => {
255-
executeCode();
256-
});
257-
258-
// If loadstop hasn't triggered after 1 second, execute the code anyway.
259-
setTimeout(() => {
260-
executeCode();
261-
}, 1000);
237+
if (this.appProvider.isMobile()) {
238+
this.utils.openInApp(launcherUrl);
262239
} else {
263-
// Generate launched and open it in system browser, we found some cases where inapp caused JS issues.
264-
const launcherUrl = await this.generateLauncher(url, params);
265-
240+
// In desktop open in browser, we found some cases where inapp caused JS issues.
266241
this.utils.openInBrowser(launcherUrl);
267242
}
268243
}
@@ -284,6 +259,8 @@ export class AddonModLtiProvider {
284259
}
285260
}
286261

262+
export class AddonModLti extends makeSingleton(AddonModLtiProvider) {}
263+
287264
/**
288265
* LTI returned by mod_lti_get_ltis_by_courses.
289266
*/

0 commit comments

Comments
 (0)