391 lines
13 KiB
TypeScript
391 lines
13 KiB
TypeScript
// (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 { CoreError } from '@classes/errors/error';
|
|
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
|
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
|
|
import { CoreFile } from '@services/file';
|
|
import { CorePlatform } from '@services/platform';
|
|
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
|
|
import { CoreTextUtils } from '@services/utils/text';
|
|
import { CoreUrlUtils } from '@services/utils/url';
|
|
import { CoreUtils } from '@services/utils/utils';
|
|
import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
|
|
import { makeSingleton, Translate } from '@singletons';
|
|
|
|
const ROOT_CACHE_KEY = 'mmaModLti:';
|
|
const LAUNCHER_FILE_NAME = 'lti_launcher.html';
|
|
|
|
/**
|
|
* Service that provides some features for LTI.
|
|
*/
|
|
@Injectable({ providedIn: 'root' })
|
|
export class AddonModLtiProvider {
|
|
|
|
static readonly COMPONENT = 'mmaModLti';
|
|
|
|
/**
|
|
* Delete launcher.
|
|
*
|
|
* @return Promise resolved when the launcher file is deleted.
|
|
*/
|
|
deleteLauncher(): Promise<void> {
|
|
return CoreFile.removeFile(LAUNCHER_FILE_NAME);
|
|
}
|
|
|
|
/**
|
|
* Generates a launcher file.
|
|
*
|
|
* @param url Launch URL.
|
|
* @param params Launch params.
|
|
* @return Promise resolved with the file URL.
|
|
*/
|
|
async generateLauncher(url: string, params: AddonModLtiParam[]): Promise<string> {
|
|
if (!CoreFile.isAvailable()) {
|
|
return url;
|
|
}
|
|
|
|
// Generate a form with the params.
|
|
let text = `<form action="${url}" name="ltiLaunchForm" method="post" encType="application/x-www-form-urlencoded">\n`;
|
|
params.forEach((p) => {
|
|
if (p.name == 'ext_submit') {
|
|
text += ' <input type="submit"';
|
|
} else {
|
|
text += ' <input type="hidden" name="' + CoreTextUtils.escapeHTML(p.name) + '"';
|
|
}
|
|
text += ' value="' + CoreTextUtils.escapeHTML(p.value) + '"/>\n';
|
|
});
|
|
text += '</form>\n';
|
|
|
|
// Add an in-line script to automatically submit the form.
|
|
text += '<script type="text/javascript"> \n' +
|
|
' window.onload = function() { \n' +
|
|
' document.ltiLaunchForm.submit(); \n' +
|
|
' }; \n' +
|
|
'</script> \n';
|
|
|
|
const entry = await CoreFile.writeFile(LAUNCHER_FILE_NAME, text);
|
|
|
|
return entry.toURL();
|
|
}
|
|
|
|
/**
|
|
* Get a LTI.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param cmId Course module ID.
|
|
* @param options Other options.
|
|
* @return Promise resolved when the LTI is retrieved.
|
|
*/
|
|
async getLti(courseId: number, cmId: number, options: CoreSitesCommonWSOptions = {}): Promise<AddonModLtiLti> {
|
|
const params: AddonModLtiGetLtisByCoursesWSParams = {
|
|
courseids: [courseId],
|
|
};
|
|
const preSets: CoreSiteWSPreSets = {
|
|
cacheKey: this.getLtiCacheKey(courseId),
|
|
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
|
component: AddonModLtiProvider.COMPONENT,
|
|
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
|
|
};
|
|
|
|
const site = await CoreSites.getSite(options.siteId);
|
|
|
|
const response = await site.read<AddonModLtiGetLtisByCoursesWSResponse>('mod_lti_get_ltis_by_courses', params, preSets);
|
|
|
|
const currentLti = response.ltis.find((lti) => lti.coursemodule == cmId);
|
|
if (currentLti) {
|
|
return currentLti;
|
|
}
|
|
|
|
throw new CoreError(Translate.instant('core.course.modulenotfound'));
|
|
}
|
|
|
|
/**
|
|
* Get cache key for LTI data WS calls.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @return Cache key.
|
|
*/
|
|
protected getLtiCacheKey(courseId: number): string {
|
|
return ROOT_CACHE_KEY + 'lti:' + courseId;
|
|
}
|
|
|
|
/**
|
|
* Get a LTI launch data.
|
|
*
|
|
* @param id LTI id.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when the launch data is retrieved.
|
|
*/
|
|
async getLtiLaunchData(id: number, siteId?: string): Promise<AddonModLtiGetToolLaunchDataWSResponse> {
|
|
const params: AddonModLtiGetToolLaunchDataWSParams = {
|
|
toolid: id,
|
|
};
|
|
|
|
// Try to avoid using cache since the "nonce" parameter is set to a timestamp.
|
|
const preSets: CoreSiteWSPreSets = {
|
|
getFromCache: false,
|
|
saveToCache: true,
|
|
emergencyCache: true,
|
|
cacheKey: this.getLtiLaunchDataCacheKey(id),
|
|
};
|
|
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
return site.read<AddonModLtiGetToolLaunchDataWSResponse>('mod_lti_get_tool_launch_data', params, preSets);
|
|
}
|
|
|
|
/**
|
|
* Get cache key for LTI launch data WS calls.
|
|
*
|
|
* @param id LTI id.
|
|
* @return Cache key.
|
|
*/
|
|
protected getLtiLaunchDataCacheKey(id: number): string {
|
|
return `${ROOT_CACHE_KEY}launch:${id}`;
|
|
}
|
|
|
|
/**
|
|
* Invalidates LTI data.
|
|
*
|
|
* @param courseId Course ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when the data is invalidated.
|
|
*/
|
|
async invalidateLti(courseId: number, siteId?: string): Promise<void> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
await site.invalidateWsCacheForKey(this.getLtiCacheKey(courseId));
|
|
}
|
|
|
|
/**
|
|
* Invalidates options.
|
|
*
|
|
* @param id LTI id.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when the data is invalidated.
|
|
*/
|
|
async invalidateLtiLaunchData(id: number, siteId?: string): Promise<void> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
await site.invalidateWsCacheForKey(this.getLtiLaunchDataCacheKey(id));
|
|
}
|
|
|
|
/**
|
|
* Check if open LTI in browser via site with auto-login is disabled.
|
|
* This setting was added in 3.11.
|
|
*
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with boolean: whether it's disabled.
|
|
*/
|
|
async isLaunchViaSiteDisabled(siteId?: string): Promise<boolean> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
return this.isLaunchViaSiteDisabledInSite(site);
|
|
}
|
|
|
|
/**
|
|
* Check if open LTI in browser via site with auto-login is disabled.
|
|
* This setting was added in 3.11.
|
|
*
|
|
* @param site Site. If not defined, current site.
|
|
* @return Whether it's disabled.
|
|
*/
|
|
isLaunchViaSiteDisabledInSite(site?: CoreSite): boolean {
|
|
site = site || CoreSites.getCurrentSite();
|
|
|
|
return !!site?.isFeatureDisabled('CoreCourseModuleDelegate_AddonModLti:launchViaSite');
|
|
}
|
|
|
|
/**
|
|
* Check if open in InAppBrowser is disabled.
|
|
* This setting was removed in Moodle 3.11 because the default behaviour of the app changed.
|
|
*
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with boolean: whether it's disabled.
|
|
*/
|
|
async isOpenInAppBrowserDisabled(siteId?: string): Promise<boolean> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
return this.isOpenInAppBrowserDisabledInSite(site);
|
|
}
|
|
|
|
/**
|
|
* Check if open in InAppBrowser is disabled.
|
|
* This setting was removed in Moodle 3.11 because the default behaviour of the app changed.
|
|
*
|
|
* @param site Site. If not defined, current site.
|
|
* @return Whether it's disabled.
|
|
*/
|
|
isOpenInAppBrowserDisabledInSite(site?: CoreSite): boolean {
|
|
site = site || CoreSites.getCurrentSite();
|
|
|
|
return !!site?.isFeatureDisabled('CoreCourseModuleDelegate_AddonModLti:openInAppBrowser');
|
|
}
|
|
|
|
/**
|
|
* Launch LTI.
|
|
*
|
|
* @param url Launch URL.
|
|
* @param params Launch params.
|
|
* @return Promise resolved when the WS call is successful.
|
|
*/
|
|
async launch(url: string, params: AddonModLtiParam[]): Promise<void> {
|
|
if (!CoreUrlUtils.isHttpURL(url)) {
|
|
throw Translate.instant('addon.mod_lti.errorinvalidlaunchurl');
|
|
}
|
|
|
|
// Generate launcher and open it.
|
|
const launcherUrl = await this.generateLauncher(url, params);
|
|
|
|
if (CorePlatform.isMobile()) {
|
|
CoreUtils.openInApp(launcherUrl);
|
|
} else {
|
|
// In desktop open in browser, we found some cases where inapp caused JS issues.
|
|
CoreUtils.openInBrowser(launcherUrl);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Report the LTI as being viewed.
|
|
*
|
|
* @param id LTI id.
|
|
* @param name Name of the lti.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved when the WS call is successful.
|
|
*/
|
|
logView(id: number, name?: string, siteId?: string): Promise<void> {
|
|
const params: AddonModLtiViewLtiWSParams = {
|
|
ltiid: id,
|
|
};
|
|
|
|
return CoreCourseLogHelper.logSingle(
|
|
'mod_lti_view_lti',
|
|
params,
|
|
AddonModLtiProvider.COMPONENT,
|
|
id,
|
|
name,
|
|
'lti',
|
|
{},
|
|
siteId,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check whether the LTI should be launched in browser via the site with auto-login.
|
|
*
|
|
* @param siteId Site ID.
|
|
* @return Promise resolved with boolean.
|
|
*/
|
|
async shouldLaunchInBrowser(siteId?: string): Promise<boolean> {
|
|
const site = await CoreSites.getSite(siteId);
|
|
|
|
if (site.isVersionGreaterEqualThan('3.11')) {
|
|
// In 3.11+, launch in browser by default unless it's disabled.
|
|
return !this.isLaunchViaSiteDisabledInSite(site);
|
|
} else {
|
|
// In old sites the default behaviour is to launch in InAppBrowser.
|
|
return this.isOpenInAppBrowserDisabledInSite(site);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
export const AddonModLti = makeSingleton(AddonModLtiProvider);
|
|
|
|
/**
|
|
* Params of mod_lti_get_ltis_by_courses WS.
|
|
*/
|
|
export type AddonModLtiGetLtisByCoursesWSParams = {
|
|
courseids?: number[]; // Array of course ids.
|
|
};
|
|
|
|
/**
|
|
* Data returned by mod_lti_get_ltis_by_courses WS.
|
|
*/
|
|
export type AddonModLtiGetLtisByCoursesWSResponse = {
|
|
ltis: AddonModLtiLti[];
|
|
warnings?: CoreWSExternalWarning[];
|
|
};
|
|
|
|
/**
|
|
* LTI returned by mod_lti_get_ltis_by_courses.
|
|
*/
|
|
export type AddonModLtiLti = {
|
|
id: number; // External tool id.
|
|
coursemodule: number; // Course module id.
|
|
course: number; // Course id.
|
|
name: string; // LTI name.
|
|
intro?: string; // The LTI intro.
|
|
introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
|
|
introfiles?: CoreWSExternalFile[];
|
|
timecreated?: number; // Time of creation.
|
|
timemodified?: number; // Time of last modification.
|
|
typeid?: number; // Type id.
|
|
toolurl?: string; // Tool url.
|
|
securetoolurl?: string; // Secure tool url.
|
|
instructorchoicesendname?: string; // Instructor choice send name.
|
|
instructorchoicesendemailaddr?: number; // Instructor choice send mail address.
|
|
instructorchoiceallowroster?: number; // Instructor choice allow roster.
|
|
instructorchoiceallowsetting?: number; // Instructor choice allow setting.
|
|
instructorcustomparameters?: string; // Instructor custom parameters.
|
|
instructorchoiceacceptgrades?: number; // Instructor choice accept grades.
|
|
grade?: number; // Enable grades.
|
|
launchcontainer?: number; // Launch container mode.
|
|
resourcekey?: string; // Resource key.
|
|
password?: string; // Shared secret.
|
|
debuglaunch?: number; // Debug launch.
|
|
showtitlelaunch?: number; // Show title launch.
|
|
showdescriptionlaunch?: number; // Show description launch.
|
|
servicesalt?: string; // Service salt.
|
|
icon?: string; // Alternative icon URL.
|
|
secureicon?: string; // Secure icon URL.
|
|
section?: number; // Course section id.
|
|
visible?: number; // Visible.
|
|
groupmode?: number; // Group mode.
|
|
groupingid?: number; // Group id.
|
|
};
|
|
|
|
/**
|
|
* Params of mod_lti_get_tool_launch_data WS.
|
|
*/
|
|
export type AddonModLtiGetToolLaunchDataWSParams = {
|
|
toolid: number; // External tool instance id.
|
|
};
|
|
|
|
/**
|
|
* Data returned by mod_lti_get_tool_launch_data WS.
|
|
*/
|
|
export type AddonModLtiGetToolLaunchDataWSResponse = {
|
|
endpoint: string; // Endpoint URL.
|
|
parameters: AddonModLtiParam[];
|
|
warnings?: CoreWSExternalWarning[];
|
|
};
|
|
|
|
/**
|
|
* Param to send to the LTI.
|
|
*/
|
|
export type AddonModLtiParam = {
|
|
name: string; // Parameter name.
|
|
value: string; // Parameter value.
|
|
};
|
|
/**
|
|
* Params of mod_lti_view_lti WS.
|
|
*/
|
|
export type AddonModLtiViewLtiWSParams = {
|
|
ltiid: number; // Lti instance id.
|
|
};
|