MOBILE-2235 h5p: Implement and use content validator

main
Dani Palou 2019-11-28 12:26:20 +01:00
parent 3da7c99fd8
commit 56faa66adc
8 changed files with 1642 additions and 142 deletions

View File

@ -1552,6 +1552,94 @@
"core.group": "moodle",
"core.groupsseparate": "moodle",
"core.groupsvisible": "moodle",
"core.h5p.additionallicenseinfo": "h5p",
"core.h5p.author": "h5p",
"core.h5p.authorcomments": "h5p",
"core.h5p.authorcommentsdescription": "h5p",
"core.h5p.authorname": "h5p",
"core.h5p.authorrole": "h5p",
"core.h5p.by": "h5p",
"core.h5p.cancellabel": "h5p",
"core.h5p.ccattribution": "h5p",
"core.h5p.ccattributionnc": "h5p",
"core.h5p.ccattributionncnd": "h5p",
"core.h5p.ccattributionncsa": "h5p",
"core.h5p.ccattributionnd": "h5p",
"core.h5p.ccattributionsa": "h5p",
"core.h5p.ccpdd": "h5p",
"core.h5p.changedby": "h5p",
"core.h5p.changedescription": "h5p",
"core.h5p.changelog": "h5p",
"core.h5p.changeplaceholder": "h5p",
"core.h5p.close": "h5p",
"core.h5p.confirmdialogbody": "h5p",
"core.h5p.confirmdialogheader": "h5p",
"core.h5p.confirmlabel": "h5p",
"core.h5p.connectionLost": "h5p",
"core.h5p.connectionReestablished": "h5p",
"core.h5p.contentCopied": "h5p",
"core.h5p.contentchanged": "h5p",
"core.h5p.contenttype": "h5p",
"core.h5p.copyright": "h5p",
"core.h5p.copyrightinfo": "h5p",
"core.h5p.copyrightstring": "h5p",
"core.h5p.copyrighttitle": "h5p",
"core.h5p.creativecommons": "h5p",
"core.h5p.date": "h5p",
"core.h5p.disablefullscreen": "h5p",
"core.h5p.download": "h5p",
"core.h5p.downloadtitle": "h5p",
"core.h5p.editor": "h5p",
"core.h5p.embed": "h5p",
"core.h5p.embedtitle": "h5p",
"core.h5p.fullscreen": "h5p",
"core.h5p.gpl": "h5p",
"core.h5p.h5ptitle": "h5p",
"core.h5p.hideadvanced": "h5p",
"core.h5p.license": "h5p",
"core.h5p.licenseCC010": "h5p",
"core.h5p.licenseCC010U": "h5p",
"core.h5p.licenseCC10": "h5p",
"core.h5p.licenseCC20": "h5p",
"core.h5p.licenseCC25": "h5p",
"core.h5p.licenseCC30": "h5p",
"core.h5p.licenseCC40": "h5p",
"core.h5p.licenseGPL": "h5p",
"core.h5p.licenseV1": "h5p",
"core.h5p.licenseV2": "h5p",
"core.h5p.licenseV3": "h5p",
"core.h5p.licensee": "h5p",
"core.h5p.licenseextras": "h5p",
"core.h5p.licenseversion": "h5p",
"core.h5p.nocopyright": "h5p",
"core.h5p.offlineDialogBody": "h5p",
"core.h5p.offlineDialogHeader": "h5p",
"core.h5p.offlineDialogRetryButtonLabel": "h5p",
"core.h5p.offlineDialogRetryMessage": "h5p",
"core.h5p.offlineSuccessfulSubmit": "h5p",
"core.h5p.originator": "h5p",
"core.h5p.pd": "h5p",
"core.h5p.pddl": "h5p",
"core.h5p.pdm": "h5p",
"core.h5p.resizescript": "h5p",
"core.h5p.resubmitScores": "h5p",
"core.h5p.reuse": "h5p",
"core.h5p.reuseContent": "h5p",
"core.h5p.reuseDescription": "h5p",
"core.h5p.showadvanced": "h5p",
"core.h5p.showless": "h5p",
"core.h5p.showmore": "h5p",
"core.h5p.size": "h5p",
"core.h5p.source": "h5p",
"core.h5p.startingover": "h5p",
"core.h5p.sublevel": "h5p",
"core.h5p.thumbnail": "h5p",
"core.h5p.title": "h5p",
"core.h5p.undisclosed": "h5p",
"core.h5p.year": "h5p",
"core.h5p.years": "h5p",
"core.h5p.yearsfrom": "h5p",
"core.h5p.yearsto": "h5p",
"core.hasdatatosync": "local_moodlemobileapp",
"core.help": "moodle",
"core.hide": "moodle",

View File

@ -1550,7 +1550,12 @@
"core.group": "Group",
"core.groupsseparate": "Separate groups",
"core.groupsvisible": "Visible groups",
"core.h5p.additionallicenseinfo": "Any additional information about the license",
"core.h5p.author": "Author",
"core.h5p.authorcomments": "Author comments",
"core.h5p.authorcommentsdescription": "Comments for the editor of the content. (This text will not be published as a part of the copyright info.)",
"core.h5p.authorname": "Author's name",
"core.h5p.authorrole": "Author's role",
"core.h5p.by": "by",
"core.h5p.cancellabel": "Cancel",
"core.h5p.ccattribution": "Attribution (CC BY)",
@ -1559,7 +1564,11 @@
"core.h5p.ccattributionncsa": "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)",
"core.h5p.ccattributionnd": "Attribution-NoDerivs (CC BY-ND)",
"core.h5p.ccattributionsa": "Attribution-ShareAlike (CC BY-SA)",
"core.h5p.ccpdd": "Public Domain Dedication (CC0)",
"core.h5p.changedby": "Changed by",
"core.h5p.changedescription": "Description of change",
"core.h5p.changelog": "Changelog",
"core.h5p.changeplaceholder": "Photo cropped, text changed, etc.",
"core.h5p.close": "Close",
"core.h5p.confirmdialogbody": "Please confirm that you wish to proceed. This action is not reversible.",
"core.h5p.confirmdialogheader": "Confirm action",
@ -1570,18 +1579,24 @@
"core.h5p.contentchanged": "This content has changed since you last used it.",
"core.h5p.contenttype": "Content Type",
"core.h5p.copyright": "Rights of use",
"core.h5p.copyrightinfo": "Copyright information",
"core.h5p.copyrightstring": "Copyright",
"core.h5p.copyrighttitle": "View copyright information for this content.",
"core.h5p.creativecommons": "Creative Commons",
"core.h5p.date": "Date",
"core.h5p.disablefullscreen": "Disable fullscreen",
"core.h5p.download": "Download",
"core.h5p.downloadtitle": "Download this content as a H5P file.",
"core.h5p.editor": "Editor",
"core.h5p.embed": "Embed",
"core.h5p.embedtitle": "View the embed code for this content.",
"core.h5p.fullscreen": "Fullscreen",
"core.h5p.gpl": "General Public License v3",
"core.h5p.h5ptitle": "Visit H5P.org to check out more cool content.",
"core.h5p.hideadvanced": "Hide advanced",
"core.h5p.license": "License",
"core.h5p.licenseCC010": "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
"core.h5p.licenseCC010U": "CC0 1.0 Universal",
"core.h5p.licenseCC10": "1.0 Generic",
"core.h5p.licenseCC20": "2.0 Generic",
"core.h5p.licenseCC25": "2.5 Generic",
@ -1591,14 +1606,18 @@
"core.h5p.licenseV1": "Version 1",
"core.h5p.licenseV2": "Version 2",
"core.h5p.licenseV3": "Version 3",
"core.h5p.licensee": "Licensee",
"core.h5p.licenseextras": "License Extras",
"core.h5p.licenseversion": "License version",
"core.h5p.nocopyright": "No copyright information available for this content.",
"core.h5p.offlineDialogBody": "We were unable to send information about your completion of this task. Please check your internet connection.",
"core.h5p.offlineDialogHeader": "Your connection to the server was lost",
"core.h5p.offlineDialogRetryButtonLabel": "Retry now",
"core.h5p.offlineDialogRetryMessage": "Retrying in :num....",
"core.h5p.offlineSuccessfulSubmit": "Successfully submitted results.",
"core.h5p.originator": "Originator",
"core.h5p.pd": "Public Domain",
"core.h5p.pddl": "Public Domain Dedication and Licence",
"core.h5p.pdm": "Public Domain Mark (PDM)",
"core.h5p.play": "Play H5P",
"core.h5p.resizescript": "Include this script on your website if you want dynamic sizing of the embedded content:",
@ -1617,6 +1636,9 @@
"core.h5p.title": "Title",
"core.h5p.undisclosed": "Undisclosed",
"core.h5p.year": "Year",
"core.h5p.years": "Year(s)",
"core.h5p.yearsfrom": "Years (from)",
"core.h5p.yearsto": "Years (to)",
"core.hasdatatosync": "This {{$a}} has offline data to be synchronised.",
"core.help": "Help",
"core.hide": "Hide",

File diff suppressed because it is too large Load Diff

View File

@ -129,8 +129,12 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy {
// Local package.
this.playerSrc = url;
} else {
// Never allow downloading in the app. This will only work if the user is allowed to change the params.
const src = this.src && this.src.replace(CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD + '=1',
CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD + '=0');
// Get auto-login URL so the user is automatically authenticated.
return this.sitesProvider.getCurrentSite().getAutoLoginUrl(this.src, false).then((url) => {
return this.sitesProvider.getCurrentSite().getAutoLoginUrl(src, false).then((url) => {
// Add the preventredirect param so the user can authenticate.
this.playerSrc = this.urlUtils.addParamsToUrl(url, {preventredirect: false});
});

View File

@ -1,5 +1,10 @@
{
"additionallicenseinfo": "Any additional information about the license",
"author": "Author",
"authorcomments": "Author comments",
"authorcommentsdescription": "Comments for the editor of the content. (This text will not be published as a part of the copyright info.)",
"authorname": "Author's name",
"authorrole": "Author's role",
"by": "by",
"cancellabel": "Cancel",
"ccattribution": "Attribution (CC BY)",
@ -8,7 +13,11 @@
"ccattributionncsa": "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)",
"ccattributionnd": "Attribution-NoDerivs (CC BY-ND)",
"ccattributionsa": "Attribution-ShareAlike (CC BY-SA)",
"ccpdd": "Public Domain Dedication (CC0)",
"changedby": "Changed by",
"changedescription": "Description of change",
"changelog": "Changelog",
"changeplaceholder": "Photo cropped, text changed, etc.",
"close": "Close",
"confirmdialogbody": "Please confirm that you wish to proceed. This action is not reversible.",
"confirmdialogheader": "Confirm action",
@ -19,18 +28,24 @@
"contentchanged": "This content has changed since you last used it.",
"contenttype": "Content Type",
"copyright": "Rights of use",
"copyrightinfo": "Copyright information",
"copyrightstring": "Copyright",
"copyrighttitle": "View copyright information for this content.",
"creativecommons": "Creative Commons",
"date": "Date",
"disablefullscreen": "Disable fullscreen",
"download": "Download",
"downloadtitle": "Download this content as a H5P file.",
"editor": "Editor",
"embed": "Embed",
"embedtitle": "View the embed code for this content.",
"fullscreen": "Fullscreen",
"gpl": "General Public License v3",
"h5ptitle": "Visit H5P.org to check out more cool content.",
"hideadvanced": "Hide advanced",
"license": "License",
"licenseCC010": "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
"licenseCC010U": "CC0 1.0 Universal",
"licenseCC10": "1.0 Generic",
"licenseCC20": "2.0 Generic",
"licenseCC25": "2.5 Generic",
@ -40,14 +55,18 @@
"licenseV1": "Version 1",
"licenseV2": "Version 2",
"licenseV3": "Version 3",
"licensee": "Licensee",
"licenseextras": "License Extras",
"licenseversion": "License version",
"nocopyright": "No copyright information available for this content.",
"offlineDialogBody": "We were unable to send information about your completion of this task. Please check your internet connection.",
"offlineDialogHeader": "Your connection to the server was lost",
"offlineDialogRetryButtonLabel": "Retry now",
"offlineDialogRetryMessage": "Retrying in :num....",
"offlineSuccessfulSubmit": "Successfully submitted results.",
"originator": "Originator",
"pd": "Public Domain",
"pddl": "Public Domain Dedication and Licence",
"pdm": "Public Domain Mark (PDM)",
"play": "Play H5P",
"resizescript": "Include this script on your website if you want dynamic sizing of the embedded content:",
@ -65,5 +84,8 @@
"thumbnail": "Thumbnail",
"title": "Title",
"undisclosed": "Undisclosed",
"year": "Year"
"year": "Year",
"years": "Year(s)",
"yearsfrom": "Years (from)",
"yearsto": "Years (to)"
}

View File

@ -25,6 +25,8 @@ import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreH5PUtilsProvider } from './utils';
import { CoreH5PContentValidator } from '../classes/content-validator';
import { TranslateService } from '@ngx-translate/core';
import { FileEntry } from '@ionic-native/file';
/**
@ -304,7 +306,8 @@ export class CoreH5PProvider {
private h5pUtils: CoreH5PUtilsProvider,
private filepoolProvider: CoreFilepoolProvider,
private utils: CoreUtilsProvider,
private urlUtils: CoreUrlUtilsProvider) {
private urlUtils: CoreUrlUtilsProvider,
private translate: TranslateService) {
this.logger = logger.getInstance('CoreH5PProvider');
@ -415,6 +418,7 @@ export class CoreH5PProvider {
* @return Promise resolved with all of the files content in one string.
*/
protected concatenateFiles(assets: CoreH5PDependencyAsset[], type: string): Promise<string> {
const basePath = this.fileProvider.getBasePathInstant();
let content = '',
promise = Promise.resolve(); // Use a chain of promises so the order is kept.
@ -433,39 +437,42 @@ export class CoreH5PProvider {
if (matches && matches.length) {
matches.forEach((match) => {
let url = match.replace(/(url\([\'"]?|[\'"]?\)$)/i, '');
let url = match.replace(/(url\(['"]?|['"]?\)$)/ig, '');
if (treated[url] || url.match(/^(data:|([a-z0-9]+:)?\/)/i)) {
return; // Not relative or already treated, skip.
}
const pathSplit = assetPath.split('/');
treated[url] = url;
/* Find "../" in the URL. If it exists, we have to remove "../" and switch the last folder in the
filepath for the first folder in the url.
For instance:
Path: /H5P.Question-1.4/styles/
Url: ../images/plus-one.svg
We want this: H5P.Question-1.4/images/ITEMID/minus-one.svg. */
filepath for the first folder in the url. */
if (url.match(/^\.\.\//)) {
const pathSplit = assetPath.split('/'),
urlSplit = url.split('/').filter((i) => {
const urlSplit = url.split('/').filter((i) => {
return i; // Remove empty values.
});
// Remove the first element: ../.
urlSplit.unshift();
// Remove the file name from the asset path.
pathSplit.pop();
// Remove the first element from the file URL: ../ .
urlSplit.shift();
// Put the url's first folder into the asset path.
pathSplit[pathSplit.length - 1] = urlSplit[0];
urlSplit.shift();
// Create the new URL and replace it in the file contents.
url = '/' + pathSplit.join('/') + '/' + urlSplit.join('/');
url = pathSplit.join('/') + '/' + urlSplit.join('/');
fileContent = fileContent.replace(new RegExp(this.textUtils.escapeForRegex(match), 'g'),
'url("' + url + '")');
} else {
pathSplit[pathSplit.length - 1] = url; // Put the whole path to the end of the asset path.
url = pathSplit.join('/');
}
fileContent = fileContent.replace(new RegExp(this.textUtils.escapeForRegex(match), 'g'),
'url("' + this.textUtils.concatenatePaths(basePath, url) + '")');
});
}
@ -510,11 +517,11 @@ export class CoreH5PProvider {
url: this.getEmbedUrl(site.getURL(), h5pUrl),
contentUrl: contentUrl,
metadata: content.metadata,
contentUserData: {
0: {
contentUserData: [
{
state: '{}'
}
}
]
};
// Get the core H5P assets, needed by the H5P classes to render the H5P content.
@ -859,8 +866,7 @@ export class CoreH5PProvider {
return Promise.resolve(null);
}
const dependencies = {}, // In web, dependencies are built by the validator.
params = {
const params = {
library: this.libraryToString(content.library),
params: this.textUtils.parseJSON(content.params, false)
};
@ -869,90 +875,65 @@ export class CoreH5PProvider {
return null;
}
// Get the main library data.
return this.loadLibrary(content.library.name, content.library.majorVersion, content.library.minorVersion, siteId)
.then((library) => {
const validator = new CoreH5PContentValidator(this, this.h5pUtils, this.textUtils, this.utils, this.translate, siteId);
library.semantics = this.textUtils.parseJSON(library.semantics, '');
// Validate the main library and its dependencies.
return validator.validateLibrary(params, {options: [params.library]}).then(() => {
const depKey = 'preloaded-' + library.machineName;
let nextWeight;
// Handle addons.
return this.loadAddons(siteId);
}).then((addons) => {
// Validate addons. Use a chain of promises to calculate the weight properly.
let promise = Promise.resolve();
if (!dependencies[depKey]) {
dependencies[depKey] = {
library: library,
type: 'preloaded'
};
}
addons.forEach((addon) => {
const addTo = addon.addTo;
// Get the whole library dependency tree.
return this.findLibraryDependencies(dependencies, library, 1, false, siteId).then((weight) => {
nextWeight = weight;
dependencies[depKey].weight = nextWeight++;
if (addTo && addTo.content && addTo.content.types && addTo.content.types.length) {
for (let i = 0; i < addTo.content.types.length; i++) {
const type = addTo.content.types[i];
// Handle addons.
return this.loadAddons(siteId);
}).then((addons) => {
// Get the dependencies of all the addons. Use a chain of promises to calculate the weight properly.
let promise = Promise.resolve();
if (type && type.text && type.text.regex &&
this.h5pUtils.textAddonMatches(params.params, type.text.regex)) {
addons.forEach((addon) => {
const addTo = this.textUtils.parseJSON(addon.addTo, null);
promise = promise.then(() => {
return validator.addon(addon);
});
if (addTo && addTo.content && addTo.content.types && addTo.content.types.length) {
for (let i = 0; i < addTo.content.types.length; i++) {
const type = addTo.content.types[i];
if (type && type.text && type.text.regex &&
this.h5pUtils.textAddonMatches(params.params, type.text.regex)) {
const addonDepKey = 'preloaded-' + addon.machineName;
dependencies[addonDepKey] = {
library: addon,
type: 'preloaded'
};
promise = promise.then(() => {
return this.findLibraryDependencies(dependencies, addon, nextWeight).then((weight) => {
nextWeight = weight;
dependencies[addonDepKey].weight = nextWeight++;
});
});
break;
}
// An addon shall only be added once.
break;
}
}
});
return promise;
}).then(() => {
// Update content dependencies.
content.dependencies = dependencies;
const paramsStr = JSON.stringify(params.params);
// Sometimes the parameters are filtered before content has been created
if (content.id) {
// Update library usage.
return this.deleteLibraryUsage(content.id, siteId).catch(() => {
// Ignore errors.
}).then(() => {
return this.saveLibraryUsage(content.id, content.dependencies, siteId);
}).then(() => {
if (!content.slug) {
content.slug = this.h5pUtils.slugify(content.title);
}
// Cache.
return this.updateContentFields(content.id, {filtered: paramsStr}, siteId).then(() => {
return paramsStr;
});
});
}
return paramsStr;
});
return promise;
}).then(() => {
// Update content dependencies.
content.dependencies = validator.getDependencies();
const paramsStr = JSON.stringify(params.params);
// Sometimes the parameters are filtered before content has been created
if (content.id) {
// Update library usage.
return this.deleteLibraryUsage(content.id, siteId).catch(() => {
// Ignore errors.
}).then(() => {
return this.saveLibraryUsage(content.id, content.dependencies, siteId);
}).then(() => {
if (!content.slug) {
content.slug = this.h5pUtils.slugify(content.title);
}
// Cache.
return this.updateContentFields(content.id, {filtered: paramsStr}, siteId).then(() => {
return paramsStr;
});
});
}
return paramsStr;
}).catch(() => {
return null;
});
@ -990,12 +971,13 @@ export class CoreH5PProvider {
}
library[property].forEach((dependency: CoreH5PLibraryBasicData) => {
const dependencyKey = type + '-' + dependency.machineName;
if (dependencies[dependencyKey]) {
return; // Skip, already have this.
}
promise = promise.then(() => {
const dependencyKey = type + '-' + dependency.machineName;
if (dependencies[dependencyKey]) {
return; // Skip, already have this.
}
// Get the dependency library data and its subdependencies.
return this.loadLibrary(dependency.machineName, dependency.majorVersion, dependency.minorVersion, siteId)
.then((dependencyLibrary) => {
@ -1424,7 +1406,7 @@ export class CoreH5PProvider {
// Aggregate and store assets.
return this.cacheAssets(files, cachedAssetsHash, folderName, siteId).then(() => {
// Keep track of which libraries have been cached in case they are updated.
return this.saveCachedAssets(cachedAssetsHash, dependencies, siteId);
return this.saveCachedAssets(cachedAssetsHash, dependencies, folderName, siteId);
}).then(() => {
return files;
});
@ -1652,12 +1634,12 @@ export class CoreH5PProvider {
}
return db.getRecords(this.LIBRARIES_TABLE, conditions);
}).then((libraries) => {
}).then((libraries): any => {
if (!libraries.length) {
return Promise.reject(null);
}
return libraries[0];
return this.parseLibDBData(libraries[0]);
});
}
@ -1681,7 +1663,9 @@ export class CoreH5PProvider {
*/
protected getLibraryById(id: number, siteId?: string): Promise<CoreH5PLibraryDBData> {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
return db.getRecord(this.LIBRARIES_TABLE, {id: id});
return db.getRecord(this.LIBRARIES_TABLE, {id: id}).then((library) => {
return this.parseLibDBData(library);
});
});
}
@ -1946,7 +1930,7 @@ export class CoreH5PProvider {
const addons = [];
for (let i = 0; i < result.rows.length; i++) {
addons.push(result.rows.item(i));
addons.push(this.parseLibAddonData(result.rows.item(i)));
}
return addons;
@ -1963,6 +1947,8 @@ export class CoreH5PProvider {
* @return Promise resolved with the content data.
*/
protected loadContentData(id?: number, fileUrl?: string, siteId?: string): Promise<CoreH5PContentData> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
let promise: Promise<CoreH5PContentDBData>;
if (id) {
@ -1978,31 +1964,35 @@ export class CoreH5PProvider {
// Load the main library data.
return this.getLibraryById(contentData.mainlibraryid, siteId).then((libData) => {
// Map the values to the names used by the H5P core (it's the same Moodle web does).
return {
id: contentData.id,
params: contentData.jsoncontent,
// The embedtype will be always set to 'iframe' to prevent conflicts with JS and CSS.
embedType: 'iframe',
disable: null,
folderName: contentData.foldername,
title: libData.title,
slug: this.h5pUtils.slugify(libData.title) + '-' + contentData.id,
filtered: contentData.filtered,
libraryMajorVersion: libData.majorversion,
libraryMinorVersion: libData.minorversion,
metadata: {
license: 'U' // Stop "invalid selected option in select" for old content without license chosen.
},
library: {
id: libData.id,
name: libData.machinename,
majorVersion: libData.majorversion,
minorVersion: libData.minorversion,
embedTypes: libData.embedtypes,
fullscreen: libData.fullscreen
}
};
// Validate metadata.
const validator = new CoreH5PContentValidator(this, this.h5pUtils, this.textUtils, this.utils, this.translate,
siteId);
// Validate empty metadata, like Moodle web does.
return validator.validateMetadata({}).then((metadata) => {
// Map the values to the names used by the H5P core (it's the same Moodle web does).
return {
id: contentData.id,
params: contentData.jsoncontent,
embedType: 'iframe', // Always use iframe.
disable: null,
folderName: contentData.foldername,
title: libData.title,
slug: this.h5pUtils.slugify(libData.title) + '-' + contentData.id,
filtered: contentData.filtered,
libraryMajorVersion: libData.majorversion,
libraryMinorVersion: libData.minorversion,
metadata: metadata,
library: {
id: libData.id,
name: libData.machinename,
majorVersion: libData.majorversion,
minorVersion: libData.minorversion,
embedTypes: libData.embedtypes,
fullscreen: libData.fullscreen
}
};
});
});
});
}
@ -2113,6 +2103,31 @@ export class CoreH5PProvider {
});
}
/**
* Parse library addon data.
*
* @param library Library addon data.
* @return Parsed library.
*/
parseLibAddonData(library: any): CoreH5PLibraryAddonData {
library.addto = this.textUtils.parseJSON(library.addto, null);
return library;
}
/**
* Parse library DB data.
*
* @param library Library DB data.
* @return Parsed library.
*/
parseLibDBData(library: any): CoreH5PLibraryDBData {
library.semantics = this.textUtils.parseJSON(library.semantics, null);
library.addto = this.textUtils.parseJSON(library.addto, null);
return library;
}
/**
* Process libraries from an H5P library, getting the required data to save them.
* This code was copied from the isValidPackage function in Moodle's H5PValidator.
@ -2172,12 +2187,13 @@ export class CoreH5PProvider {
* know which cache file to delete when a library is updated.
*
* @param key Hash key for the given libraries.
* @param libraries List of dependencies used to create the key
* @param libraries List of dependencies used to create the key.
* @param folderName The name of the folder that contains the H5P.
* @param siteId The site ID.
* @return Promise resolved when done.
*/
protected saveCachedAssets(hash: string, dependencies: {[machineName: string]: CoreH5PContentDependencyData},
siteId?: string): Promise<any> {
folderName: string, siteId?: string): Promise<any> {
return this.sitesProvider.getSiteDb(siteId).then((db) => {
const promises = [];
@ -2185,7 +2201,8 @@ export class CoreH5PProvider {
for (const key in dependencies) {
const data = {
hash: key,
libraryid: dependencies[key].libraryId
libraryid: dependencies[key].libraryId,
foldername: folderName
};
promises.push(db.insertRecord(this.LIBRARIES_CACHEDASSETS_TABLE, data));
@ -2279,7 +2296,7 @@ export class CoreH5PProvider {
for (const libString in librariesJsonData) {
const libraryData = librariesJsonData[libString];
// Find local library identifier
// Find local library identifier.
promises.push(this.getLibraryByData(libraryData).catch(() => {
// Not found.
}).then((dbData) => {
@ -2422,7 +2439,7 @@ export class CoreH5PProvider {
preloadedjs: preloadedJS,
preloadedcss: preloadedCSS,
droplibrarycss: dropLibraryCSS,
semantics: libraryData.semantics,
semantics: typeof libraryData.semantics != 'undefined' ? JSON.stringify(libraryData.semantics) : null,
addto: typeof libraryData.addTo != 'undefined' ? JSON.stringify(libraryData.addTo) : null,
};
@ -2497,8 +2514,8 @@ export class CoreH5PProvider {
for (const key in librariesInUse) {
const dependency = librariesInUse[key];
if (dependency.library.dropLibraryCss) {
const split = dependency.library.dropLibraryCss.split(', ');
if ((<CoreH5PLibraryData> dependency.library).dropLibraryCss) {
const split = (<CoreH5PLibraryData> dependency.library).dropLibraryCss.split(', ');
split.forEach((css) => {
dropLibraryCssList[css] = css;
@ -2739,7 +2756,7 @@ export type CoreH5PContentDependencyData = {
* Data for each content dependency in the dependency tree.
*/
export type CoreH5PContentDepsTreeDependency = {
library: CoreH5PLibraryData; // Library data.
library: CoreH5PLibraryData | CoreH5PLibraryAddonData; // Library data.
type: string; // Dependency type.
weight?: number; // An integer determining the order of the libraries when they are loaded.
};
@ -2786,7 +2803,7 @@ export type CoreH5PLibraryAddonData = {
patchVersion: number; // Patch version.
preloadedJs?: string; // Comma separated list of scripts to load.
preloadedCss?: string; // Comma separated list of stylesheets to load.
addTo?: string; // Plugin configuration data.
addTo?: any; // Plugin configuration data.
};
/**
@ -2805,8 +2822,8 @@ export type CoreH5PLibraryDBData = {
preloadedjs?: string; // Comma separated list of scripts to load.
preloadedcss?: string; // Comma separated list of stylesheets to load.
droplibrarycss?: string; // List of libraries that should not have CSS included if this library is used. Comma separated list.
semantics?: string; // The semantics definition in json format.
addto?: string; // Plugin configuration data.
semantics?: any; // The semantics definition.
addto?: any; // Plugin configuration data.
};
/**

View File

@ -313,6 +313,27 @@ export class CoreH5PUtilsProvider {
};
}
/**
* Parses library data from a string on the form {machineName} {majorVersion}.{minorVersion}.
*
* @param libraryString On the form {machineName} {majorVersion}.{minorVersion}
* @return Object with keys machineName, majorVersion and minorVersion. Null if string is not parsable.
*/
libraryFromString(libraryString: string): {machineName: string, majorVersion: number, minorVersion: number} {
const matches = libraryString.match(/^([\w0-9\-\.]{1,255})[\-\ ]([0-9]{1,5})\.([0-9]{1,5})$/i);
if (matches && matches.length >= 4) {
return {
machineName: matches[1],
majorVersion: Number(matches[2]),
minorVersion: Number(matches[3])
};
}
return null;
}
/**
* Convert list of library parameter values to csv.
*

View File

@ -135,17 +135,19 @@ export class CoreUtilsProvider {
/**
* Converts an array of objects to an object, using a property of each entry as the key.
* It can also be used to convert an array of strings to an object where the keys are the elements of the array.
* E.g. [{id: 10, name: 'A'}, {id: 11, name: 'B'}] => {10: {id: 10, name: 'A'}, 11: {id: 11, name: 'B'}}
*
* @param array The array to convert.
* @param propertyName The name of the property to use as the key.
* @param propertyName The name of the property to use as the key. If not provided, the whole item will be used.
* @param result Object where to put the properties. If not defined, a new object will be created.
* @return The object.
*/
arrayToObject(array: any[], propertyName: string, result?: any): any {
arrayToObject(array: any[], propertyName?: string, result?: any): any {
result = result || {};
array.forEach((entry) => {
result[entry[propertyName]] = entry;
const key = propertyName ? entry[propertyName] : entry;
result[key] = entry;
});
return result;