diff --git a/src/addon/mod/lti/components/components.module.ts b/src/addon/mod/lti/components/components.module.ts
new file mode 100644
index 000000000..d734384b9
--- /dev/null
+++ b/src/addon/mod/lti/components/components.module.ts
@@ -0,0 +1,45 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { CoreComponentsModule } from '@components/components.module';
+import { CoreDirectivesModule } from '@directives/directives.module';
+import { CoreCourseComponentsModule } from '@core/course/components/components.module';
+import { AddonModLtiIndexComponent } from './index/index';
+
+@NgModule({
+ declarations: [
+ AddonModLtiIndexComponent,
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreComponentsModule,
+ CoreDirectivesModule,
+ CoreCourseComponentsModule
+ ],
+ providers: [
+ ],
+ exports: [
+ AddonModLtiIndexComponent,
+ ],
+ entryComponents: [
+ AddonModLtiIndexComponent,
+ ]
+})
+export class AddonModLtiComponentsModule {}
diff --git a/src/addon/mod/lti/components/index/index.html b/src/addon/mod/lti/components/index/index.html
new file mode 100644
index 000000000..e80a89554
--- /dev/null
+++ b/src/addon/mod/lti/components/index/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'addon.mod_lti.errorinvalidlaunchurl' | translate }}
+
diff --git a/src/addon/mod/lti/components/index/index.ts b/src/addon/mod/lti/components/index/index.ts
new file mode 100644
index 000000000..4c81f8786
--- /dev/null
+++ b/src/addon/mod/lti/components/index/index.ts
@@ -0,0 +1,118 @@
+// (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, Optional, Injector } from '@angular/core';
+import { Content } from 'ionic-angular';
+import { CoreUrlUtilsProvider } from '@providers/utils/url';
+import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component';
+import { AddonModLtiProvider } from '../../providers/lti';
+
+/**
+ * Component that displays an LTI entry page.
+ */
+@Component({
+ selector: 'addon-mod-lti-index',
+ templateUrl: 'index.html',
+})
+export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityComponent {
+ component = AddonModLtiProvider.COMPONENT;
+ moduleName = 'lti';
+
+ lti: any; // The LTI object.
+ isValidUrl: boolean;
+
+ protected fetchContentDefaultError = 'addon.mod_lti.errorgetlti';
+
+ constructor(injector: Injector,
+ @Optional() protected content: Content,
+ private ltiProvider: AddonModLtiProvider,
+ private urlUtils: CoreUrlUtilsProvider) {
+ super(injector, content);
+ }
+
+ /**
+ * Component being initialized.
+ */
+ ngOnInit(): void {
+ super.ngOnInit();
+
+ this.loadContent(false, true);
+ }
+
+ /**
+ * Check the completion.
+ */
+ protected checkCompletion(): void {
+ this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);
+ }
+
+ /**
+ * Get the LTI data.
+ *
+ * @param {boolean} [refresh=false] If it's refreshing content.
+ * @param {boolean} [sync=false] If the refresh is needs syncing.
+ * @param {boolean} [showErrors=false] If show errors to the user of hide them.
+ * @return {Promise} Promise resolved when done.
+ */
+ protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise {
+ return this.ltiProvider.getLti(this.courseId, this.module.id).then((ltiData) => {
+ this.lti = ltiData;
+
+ return this.ltiProvider.getLtiLaunchData(ltiData.id).then((launchData) => {
+ this.lti.launchdata = launchData;
+ this.description = this.lti.intro || this.description;
+ this.isValidUrl = this.urlUtils.isHttpURL(launchData.endpoint);
+ this.dataRetrieved.emit(this.lti);
+ });
+ }).then(() => {
+ // All data obtained, now fill the context menu.
+ this.fillContextMenu(refresh);
+ });
+ }
+
+ /**
+ * Perform the invalidate content function.
+ *
+ * @return {Promise} Resolved when done.
+ */
+ protected invalidateContent(): Promise {
+ const promises = [];
+
+ promises.push(this.ltiProvider.invalidateLti(this.courseId));
+ if (this.lti) {
+ promises.push(this.ltiProvider.invalidateLtiLaunchData(this.lti.id));
+ }
+
+ return Promise.all(promises);
+ }
+
+ /**
+ * Launch the LTI.
+ */
+ launch(): void {
+ // "View" LTI.
+ this.ltiProvider.logView(this.lti.id).then(() => {
+ this.checkCompletion();
+ }).catch((error) => {
+ // Ignore errors.
+ });
+
+ // Launch LTI.
+ this.ltiProvider.launch(this.lti.launchdata.endpoint, this.lti.launchdata.parameters).catch((message) => {
+ if (message) {
+ this.domUtils.showErrorModal(message);
+ }
+ });
+ }
+}
diff --git a/src/addon/mod/lti/lang/en.json b/src/addon/mod/lti/lang/en.json
new file mode 100644
index 000000000..3dc7ad1a5
--- /dev/null
+++ b/src/addon/mod/lti/lang/en.json
@@ -0,0 +1,5 @@
+{
+ "errorgetlti": "Error getting module data.",
+ "errorinvalidlaunchurl": "The launch URL is not valid.",
+ "launchactivity": "Launch the activity"
+}
\ No newline at end of file
diff --git a/src/addon/mod/lti/lti.module.ts b/src/addon/mod/lti/lti.module.ts
new file mode 100644
index 000000000..557775b5c
--- /dev/null
+++ b/src/addon/mod/lti/lti.module.ts
@@ -0,0 +1,41 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { AddonModLtiComponentsModule } from './components/components.module';
+import { AddonModLtiModuleHandler } from './providers/module-handler';
+import { AddonModLtiProvider } from './providers/lti';
+import { AddonModLtiLinkHandler } from './providers/link-handler';
+import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
+import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate';
+
+@NgModule({
+ declarations: [
+ ],
+ imports: [
+ AddonModLtiComponentsModule
+ ],
+ providers: [
+ AddonModLtiProvider,
+ AddonModLtiModuleHandler,
+ AddonModLtiLinkHandler,
+ ]
+})
+export class AddonModLtiModule {
+ constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModLtiModuleHandler,
+ contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModLtiLinkHandler) {
+ moduleDelegate.registerHandler(moduleHandler);
+ contentLinksDelegate.registerHandler(linkHandler);
+ }
+}
diff --git a/src/addon/mod/lti/pages/index/index.html b/src/addon/mod/lti/pages/index/index.html
new file mode 100644
index 000000000..456646402
--- /dev/null
+++ b/src/addon/mod/lti/pages/index/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/addon/mod/lti/pages/index/index.module.ts b/src/addon/mod/lti/pages/index/index.module.ts
new file mode 100644
index 000000000..dda226660
--- /dev/null
+++ b/src/addon/mod/lti/pages/index/index.module.ts
@@ -0,0 +1,33 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { NgModule } from '@angular/core';
+import { IonicPageModule } from 'ionic-angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { CoreDirectivesModule } from '@directives/directives.module';
+import { AddonModLtiComponentsModule } from '../../components/components.module';
+import { AddonModLtiIndexPage } from './index';
+
+@NgModule({
+ declarations: [
+ AddonModLtiIndexPage,
+ ],
+ imports: [
+ CoreDirectivesModule,
+ AddonModLtiComponentsModule,
+ IonicPageModule.forChild(AddonModLtiIndexPage),
+ TranslateModule.forChild()
+ ],
+})
+export class AddonModLtiIndexPageModule {}
diff --git a/src/addon/mod/lti/pages/index/index.ts b/src/addon/mod/lti/pages/index/index.ts
new file mode 100644
index 000000000..ea6ab0034
--- /dev/null
+++ b/src/addon/mod/lti/pages/index/index.ts
@@ -0,0 +1,48 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Component, ViewChild } from '@angular/core';
+import { IonicPage, NavParams } from 'ionic-angular';
+import { AddonModLtiIndexComponent } from '../../components/index/index';
+
+/**
+ * Page that displays an LTI.
+ */
+@IonicPage({ segment: 'addon-mod-lti-index' })
+@Component({
+ selector: 'page-addon-mod-lti-index',
+ templateUrl: 'index.html',
+})
+export class AddonModLtiIndexPage {
+ @ViewChild(AddonModLtiIndexComponent) ltiComponent: AddonModLtiIndexComponent;
+
+ title: string;
+ module: any;
+ courseId: number;
+
+ constructor(navParams: NavParams) {
+ this.module = navParams.get('module') || {};
+ this.courseId = navParams.get('courseId');
+ this.title = this.module.name;
+ }
+
+ /**
+ * Update some data based on the LTI instance.
+ *
+ * @param {any} lti LTI instance.
+ */
+ updateData(lti: any): void {
+ this.title = lti.name || this.title;
+ }
+}
diff --git a/src/addon/mod/lti/providers/link-handler.ts b/src/addon/mod/lti/providers/link-handler.ts
new file mode 100644
index 000000000..2c69182cb
--- /dev/null
+++ b/src/addon/mod/lti/providers/link-handler.ts
@@ -0,0 +1,29 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Injectable } from '@angular/core';
+import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler';
+import { CoreCourseHelperProvider } from '@core/course/providers/helper';
+
+/**
+ * Handler to treat links to LTI.
+ */
+@Injectable()
+export class AddonModLtiLinkHandler extends CoreContentLinksModuleIndexHandler {
+ name = 'AddonModLtiLinkHandler';
+
+ constructor(courseHelper: CoreCourseHelperProvider) {
+ super(courseHelper, 'AddonModLti', 'lti');
+ }
+}
diff --git a/src/addon/mod/lti/providers/lti.ts b/src/addon/mod/lti/providers/lti.ts
new file mode 100644
index 000000000..be87ee4ac
--- /dev/null
+++ b/src/addon/mod/lti/providers/lti.ts
@@ -0,0 +1,216 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Injectable } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { CoreFileProvider } from '@providers/file';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreTextUtilsProvider } from '@providers/utils/text';
+import { CoreUtilsProvider } from '@providers/utils/utils';
+import { CoreUrlUtilsProvider } from '@providers/utils/url';
+
+export interface AddonModLtiParam {
+ name: string;
+ value: string;
+}
+
+/**
+ * Service that provides some features for LTI.
+ */
+@Injectable()
+export class AddonModLtiProvider {
+ static COMPONENT = 'mmaModLti';
+
+ protected ROOT_CACHE_KEY = 'mmaModLti:';
+ protected LAUNCHER_FILE_NAME = 'lti_launcher.html';
+
+ constructor(private fileProvider: CoreFileProvider,
+ private sitesProvider: CoreSitesProvider,
+ private textUtils: CoreTextUtilsProvider,
+ private urlUtils: CoreUrlUtilsProvider,
+ private utils: CoreUtilsProvider,
+ private translate: TranslateService) {}
+
+ /**
+ * Delete launcher.
+ *
+ * @return {Promise} Promise resolved when the launcher file is deleted.
+ */
+ deleteLauncher(): Promise {
+ return this.fileProvider.removeFile(this.LAUNCHER_FILE_NAME);
+ }
+
+ /**
+ * Generates a launcher file.
+ *
+ * @param {string} url Launch URL.
+ * @param {AddonModLtiParam[]} params Launch params.
+ * @return {Promise} Promise resolved with the file URL.
+ */
+ generateLauncher(url: string, params: AddonModLtiParam[]): Promise {
+ if (!this.fileProvider.isAvailable()) {
+ return Promise.resolve(url);
+ }
+
+ // Generate a form with the params.
+ let text = '\n';
+
+ // Add an in-line script to automatically submit the form.
+ text += ' \n';
+
+ return this.fileProvider.writeFile(this.LAUNCHER_FILE_NAME, text).then((entry) => {
+ return entry.toURL();
+ });
+ }
+
+ /**
+ * Get a LTI.
+ *
+ * @param {number} courseId Course ID.
+ * @param {number} cmId Course module ID.
+ * @return {Promise} Promise resolved when the LTI is retrieved.
+ */
+ getLti(courseId: number, cmId: number): Promise {
+ const params: any = {
+ courseids: [courseId]
+ };
+ const preSets: any = {
+ cacheKey: this.getLtiCacheKey(courseId)
+ };
+
+ return this.sitesProvider.getCurrentSite().read('mod_lti_get_ltis_by_courses', params, preSets).then((response) => {
+ if (response.ltis) {
+ const currentLti = response.ltis.find((lti) => lti.coursemodule == cmId);
+ if (currentLti) {
+ return currentLti;
+ }
+ }
+
+ return Promise.reject(null);
+ });
+ }
+
+ /**
+ * Get cache key for LTI data WS calls.
+ *
+ * @param {number} courseId Course ID.
+ * @return {string} Cache key.
+ */
+ protected getLtiCacheKey(courseId: number): string {
+ return this.ROOT_CACHE_KEY + 'lti:' + courseId;
+ }
+
+ /**
+ * Get a LTI launch data.
+ *
+ * @param {number} id LTI id.
+ * @return {Promise} Promise resolved when the launch data is retrieved.
+ */
+ getLtiLaunchData(id: number): Promise {
+ const params: any = {
+ toolid: id
+ };
+ const preSets = {
+ cacheKey: this.getLtiLaunchDataCacheKey(id)
+ };
+
+ return this.sitesProvider.getCurrentSite().read('mod_lti_get_tool_launch_data', params, preSets).then((response) => {
+ if (response.endpoint) {
+ return response;
+ }
+
+ return Promise.reject(null);
+ });
+ }
+
+ /**
+ * Get cache key for LTI launch data WS calls.
+ *
+ * @param {number} id LTI id.
+ * @return {string} Cache key.
+ */
+ protected getLtiLaunchDataCacheKey(id: number): string {
+ return this.ROOT_CACHE_KEY + 'launch:' + id;
+ }
+
+ /**
+ * Invalidates LTI data.
+ *
+ * @param {number} courseId Course ID.
+ * @return {Promise} Promise resolved when the data is invalidated.
+ */
+ invalidateLti(courseId: number): Promise {
+ return this.sitesProvider.getCurrentSite().invalidateWsCacheForKey(this.getLtiCacheKey(courseId));
+ }
+
+ /**
+ * Invalidates options.
+ *
+ * @param {number} id LTI id.
+ * @return {Promise} Promise resolved when the data is invalidated.
+ */
+ invalidateLtiLaunchData(id: number): Promise {
+ return this.sitesProvider.getCurrentSite().invalidateWsCacheForKey(this.getLtiLaunchDataCacheKey(id));
+ }
+
+ /**
+ * Launch LTI.
+ *
+ * @param {string} url Launch URL.
+ * @param {AddonModLtiParam[]} params Launch params.
+ * @return {Promise} Promise resolved when the WS call is successful.
+ */
+ launch(url: string, params: AddonModLtiParam[]): Promise {
+ if (!this.urlUtils.isHttpURL(url)) {
+ return Promise.reject(this.translate.instant('addon.mod_lti.errorinvalidlaunchurl'));
+ }
+
+ // Generate launcher and open it.
+ return this.generateLauncher(url, params).then((url) => {
+ this.utils.openInApp(url).show();
+ });
+ }
+
+ /**
+ * Report the LTI as being viewed.
+ *
+ * @param {string} id LTI id.
+ * @return {Promise} Promise resolved when the WS call is successful.
+ */
+ logView(id: string): Promise {
+ if (id) {
+ const params: any = {
+ ltiid: id
+ };
+
+ return this.sitesProvider.getCurrentSite().write('mod_lti_view_lti', params);
+ }
+
+ return Promise.reject(null);
+ }
+}
diff --git a/src/addon/mod/lti/providers/module-handler.ts b/src/addon/mod/lti/providers/module-handler.ts
new file mode 100644
index 000000000..dfeff3028
--- /dev/null
+++ b/src/addon/mod/lti/providers/module-handler.ts
@@ -0,0 +1,126 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Injectable } from '@angular/core';
+import { NavController, NavOptions } from 'ionic-angular';
+import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate';
+import { CoreAppProvider } from '@providers/app';
+import { CoreCourseProvider } from '@core/course/providers/course';
+import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { CoreFilepoolProvider } from '@providers/filepool';
+import { CoreSitesProvider } from '@providers/sites';
+import { AddonModLtiIndexComponent } from '../components/index/index';
+import { AddonModLtiProvider } from './lti';
+
+/**
+ * Handler to support LTI modules.
+ */
+@Injectable()
+export class AddonModLtiModuleHandler implements CoreCourseModuleHandler {
+ name = 'AddonModLti';
+ modName = 'lti';
+
+ constructor(private appProvider: CoreAppProvider,
+ private courseProvider: CoreCourseProvider,
+ private domUtils: CoreDomUtilsProvider,
+ private filepoolProvider: CoreFilepoolProvider,
+ private sitesProvider: CoreSitesProvider,
+ private ltiProvider: AddonModLtiProvider) {}
+
+ /**
+ * Check if the handler is enabled on a site level.
+ *
+ * @return {boolean|Promise} Whether or not the handler is enabled on a site level.
+ */
+ isEnabled(): boolean | Promise {
+ return true;
+ }
+
+ /**
+ * Get the data required to display the module in the course contents view.
+ *
+ * @param {any} module The module object.
+ * @param {number} courseId The course ID.
+ * @param {number} sectionId The section ID.
+ * @return {CoreCourseModuleHandlerData} Data to render the module.
+ */
+ getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData {
+ const data: CoreCourseModuleHandlerData = {
+ icon: this.courseProvider.getModuleIconSrc('lti'),
+ title: module.name,
+ class: 'addon-mod_lti-handler',
+ action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void {
+ navCtrl.push('AddonModLtiIndexPage', {module: module, courseId: courseId}, options);
+ },
+ buttons: [{
+ icon: 'link',
+ label: 'addon.mod_lti.launchactivity',
+ action: (event: Event, navCtrl: NavController, module: any, courseId: number): void => {
+ const modal = this.domUtils.showModalLoading();
+
+ // Get LTI and launch data.
+ this.ltiProvider.getLti(courseId, module.id).then((ltiData) => {
+ return this.ltiProvider.getLtiLaunchData(ltiData.id).then((launchData) => {
+ // "View" LTI.
+ this.ltiProvider.logView(ltiData.id).then(() => {
+ this.courseProvider.checkModuleCompletion(courseId, module.completionstatus);
+ }).catch(() => {
+ // Ignore errors.
+ });
+
+ // Launch LTI.
+ return this.ltiProvider.launch(launchData.endpoint, launchData.parameters);
+ });
+ }).catch((message) => {
+ this.domUtils.showErrorModalDefault(message, 'addon.mod_lti.errorgetlti', true);
+ }).finally(() => {
+ modal.dismiss();
+ });
+ }
+ }]
+ };
+
+ // Handle custom icons.
+ this.ltiProvider.getLti(courseId, module.id).then((ltiData) => {
+ const icon = ltiData.secureicon || ltiData.icon;
+ if (icon) {
+ const siteId = this.sitesProvider.getCurrentSiteId();
+ this.filepoolProvider.downloadUrl(siteId, icon, false, AddonModLtiProvider.COMPONENT, module.id).then((url) => {
+ data.icon = url;
+ }).catch(() => {
+ // Error downloading. If we're online we'll set the online url.
+ if (this.appProvider.isOnline()) {
+ data.icon = icon;
+ }
+ });
+ }
+ }).catch(() => {
+ // Ignore errors.
+ });
+
+ return data;
+ }
+
+ /**
+ * Get the component to render the module. This is needed to support singleactivity course format.
+ * The component returned must implement CoreCourseModuleMainComponent.
+ *
+ * @param {any} course The course object.
+ * @param {any} module The module object.
+ * @return {any} The component to use, undefined if not found.
+ */
+ getMainComponent(course: any, module: any): any {
+ return AddonModLtiIndexComponent;
+ }
+}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 37abd9b1d..7c565ae77 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -82,6 +82,7 @@ import { AddonModBookModule } from '@addon/mod/book/book.module';
import { AddonModChatModule } from '@addon/mod/chat/chat.module';
import { AddonModChoiceModule } from '@addon/mod/choice/choice.module';
import { AddonModLabelModule } from '@addon/mod/label/label.module';
+import { AddonModLtiModule } from '@addon/mod/lti/lti.module';
import { AddonModResourceModule } from '@addon/mod/resource/resource.module';
import { AddonModFeedbackModule } from '@addon/mod/feedback/feedback.module';
import { AddonModFolderModule } from '@addon/mod/folder/folder.module';
@@ -187,6 +188,7 @@ export const CORE_PROVIDERS: any[] = [
AddonModFeedbackModule,
AddonModFolderModule,
AddonModForumModule,
+ AddonModLtiModule,
AddonModPageModule,
AddonModQuizModule,
AddonModScormModule,