From 5903975e8cdb6eb63dbaecf48c1cebfeeff7fc0e Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 26 Nov 2019 08:49:59 +0100 Subject: [PATCH] MOBILE-2235 h5p: Handle display options --- .../h5p/assets/moodle/js/displayoptions.js | 35 ++++++ .../h5p/components/h5p-player/h5p-player.ts | 4 +- src/core/h5p/providers/h5p.ts | 107 +++++++++++++----- src/providers/utils/url.ts | 10 +- 4 files changed, 124 insertions(+), 32 deletions(-) create mode 100644 src/core/h5p/assets/moodle/js/displayoptions.js diff --git a/src/core/h5p/assets/moodle/js/displayoptions.js b/src/core/h5p/assets/moodle/js/displayoptions.js new file mode 100644 index 000000000..59088886d --- /dev/null +++ b/src/core/h5p/assets/moodle/js/displayoptions.js @@ -0,0 +1,35 @@ +// (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. + +/** + * Handle display options included in the URL and put them in the H5PIntegration object if it exists. + */ + +if (window.H5PIntegration && window.H5PIntegration.contents && location.search) { + var contentData = window.H5PIntegration.contents[Object.keys(window.H5PIntegration.contents)[0]]; + + if (contentData) { + contentData.displayOptions = contentData.displayOptions || {}; + + var search = location.search.replace(/^\?/, ''), + split = search.split('&'); + + split.forEach(function(param) { + var nameAndValue = param.split('='); + if (nameAndValue.length == 2) { + contentData.displayOptions[nameAndValue[0]] = nameAndValue[1] === '1' || nameAndValue[1] === 'true'; + } + }); + } +} diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index b4058b4dd..1a1cc7fb1 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -105,7 +105,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { if (this.canDownload && (this.state == CoreConstants.DOWNLOADED || this.state == CoreConstants.OUTDATED)) { // Package is downloaded, use the local URL. - promise = this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.siteId).catch(() => { + promise = this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.urlParams, this.siteId).catch(() => { // Index file doesn't exist, probably deleted because a lib was updated. Try to create it again. return this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.urlParams.url).then((path) => { @@ -114,7 +114,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { return this.h5pProvider.extractH5PFile(this.urlParams.url, file, this.siteId); }).then(() => { // File treated. Try to get the index file URL again. - return this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.siteId); + return this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.urlParams, this.siteId); }); }).catch((error) => { // Still failing. Delete the H5P package? diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts index 6dc9f3814..e6c9e76e9 100644 --- a/src/core/h5p/providers/h5p.ts +++ b/src/core/h5p/providers/h5p.ts @@ -100,7 +100,7 @@ export class CoreH5PProvider { notNull: true }, { - name: 'displayoptions', + name: 'displayoptions', // Not used right now, but we keep the field to be consistent with Moodle web. type: 'INTEGER' }, { @@ -494,10 +494,7 @@ export class CoreH5PProvider { return this.sitesProvider.getSite(siteId).then((site) => { - const disable = typeof content.disable != 'undefined' && content.disable != null ? - content.disable : CoreH5PProvider.DISABLE_NONE, - displayOptions = this.getDisplayOptionsForView(disable, id), - contentId = this.getContentId(id), + const contentId = this.getContentId(id), basePath = this.fileProvider.getBasePathInstant(), contentUrl = this.textUtils.concatenatePaths(basePath, this.getContentFolderPath(content.folderName, site.getId())); @@ -506,10 +503,10 @@ export class CoreH5PProvider { library: this.libraryToString(content.library), fullScreen: content.library.fullscreen, exportUrl: '', // We'll never display the download button, so we don't need the exportUrl. - embedCode: this.getEmbedCode(site.getURL(), h5pUrl, displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED]), + embedCode: this.getEmbedCode(site.getURL(), h5pUrl, true), resizeCode: this.getResizeCode(), title: content.slug, - displayOptions: displayOptions, + displayOptions: {}, url: this.getEmbedUrl(site.getURL(), h5pUrl), contentUrl: contentUrl, metadata: content.metadata, @@ -537,6 +534,10 @@ export class CoreH5PProvider { html += ''; + // Add our own script to handle the display options. + html += ''; + html += ''; // Include the required JS at the beginning of the body, like Moodle web does. @@ -1218,17 +1219,26 @@ export class CoreH5PProvider { * Get the content index file. * * @param fileUrl URL of the H5P package. + * @param urlParams URL params. * @param siteId The site ID. If not defined, current site. * @return Promise resolved with the file URL if exists, rejected otherwise. */ - getContentIndexFileUrl(fileUrl: string, siteId?: string): Promise { + getContentIndexFileUrl(fileUrl: string, urlParams?: {[name: string]: string}, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); return this.getContentFolderNameByUrl(fileUrl, siteId).then((folderName) => { return this.fileProvider.getFile(this.getContentIndexPath(folderName, siteId)); }).then((file) => { return file.toURL(); + }).then((url) => { + // Add display options to the URL. + return this.getContentDataByUrl(fileUrl, siteId).then((data) => { + const options = this.validateDisplayOptions(this.getDisplayOptionsFromUrlParams(urlParams), data.id); + + return this.urlUtils.addParamsToUrl(url, options, undefined, true); + }); }); + } /** @@ -1486,25 +1496,31 @@ export class CoreH5PProvider { * @return Display options as object. */ getDisplayOptionsForView(disable: number, id: number): CoreH5PDisplayOptions { - const displayOptions = this.getDisplayOptionsAsObject(disable); + return this.validateDisplayOptions(this.getDisplayOptionsAsObject(disable), id); + } - if (this.getOption(CoreH5PProvider.DISPLAY_OPTION_FRAME, true) == false) { - displayOptions[CoreH5PProvider.DISPLAY_OPTION_FRAME] = false; - } else { - displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD] = this.setDisplayOptionOverrides( - CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD, CoreH5PPermission.DOWNLOAD_H5P, id, - displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD]); + /** + * Get display options from a URL params. + * + * @param params URL params. + * @return Display options as object. + */ + getDisplayOptionsFromUrlParams(params: {[name: string]: string}): CoreH5PDisplayOptions { + const displayOptions: CoreH5PDisplayOptions = {}; - displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED] = this.setDisplayOptionOverrides( - CoreH5PProvider.DISPLAY_OPTION_EMBED, CoreH5PPermission.EMBED_H5P, id, - displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED]); - - if (this.getOption(CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT, true) == false) { - displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT] = false; - } + if (!params) { + return displayOptions; } - displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPY] = this.hasPermission(CoreH5PPermission.COPY_H5P, id); + displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD] = + this.utils.isTrueOrOne(params[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD]); + displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED] = + this.utils.isTrueOrOne(params[CoreH5PProvider.DISPLAY_OPTION_EMBED]); + displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT] = + this.utils.isTrueOrOne(params[CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT]); + displayOptions[CoreH5PProvider.DISPLAY_OPTION_FRAME] = displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD] || + displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED] || displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT]; + displayOptions[CoreH5PProvider.DISPLAY_OPTION_ABOUT] = !!this.getOption(CoreH5PProvider.DISPLAY_OPTION_ABOUT, true); return displayOptions; } @@ -1734,7 +1750,7 @@ export class CoreH5PProvider { */ getOption(name: string, defaultValue: any = false): any { // For now, all them are disabled by default, so only will be rendered when defined in the displayoptions DB field. - return 2; // CONTROLLED_BY_AUTHOR_DEFAULT_OFF. + return CoreH5PDisplayOptionBehaviour.CONTROLLED_BY_AUTHOR_DEFAULT_OFF; // CONTROLLED_BY_AUTHOR_DEFAULT_OFF. } /** @@ -1845,7 +1861,8 @@ export class CoreH5PProvider { * @return Whether the user has permission to execute an action. */ hasPermission(permission: number, id: number): boolean { - return true; + // H5P capabilities have not been introduced. + return null; } /** @@ -1967,7 +1984,7 @@ export class CoreH5PProvider { params: contentData.jsoncontent, // The embedtype will be always set to 'iframe' to prevent conflicts with JS and CSS. embedType: 'iframe', - disable: contentData.displayoptions, + disable: null, folderName: contentData.foldername, title: libData.title, slug: this.h5pUtils.slugify(libData.title) + '-' + contentData.id, @@ -2193,7 +2210,7 @@ export class CoreH5PProvider { const data: any = { jsoncontent: content.params, - displayoptions: content.disable, + displayoptions: null, mainlibraryid: content.library.libraryId, timemodified: Date.now(), filtered: null, @@ -2563,6 +2580,40 @@ export class CoreH5PProvider { return db.updateRecords(this.CONTENT_TABLE, data, {id: id}); }); } + + /** + * Validate display options, updating them if needed. + * + * @param displayOptions The display options to validate. + * @param id Package ID. + */ + validateDisplayOptions(displayOptions: CoreH5PDisplayOptions, id: number): CoreH5PDisplayOptions { + + // Never allow downloading in the app. + displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD] = false; + + // Embed - force setting it if always on or always off. In web, this is done when storing in DB. + const embed = this.getOption(CoreH5PProvider.DISPLAY_OPTION_EMBED, CoreH5PDisplayOptionBehaviour.ALWAYS_SHOW); + if (embed == CoreH5PDisplayOptionBehaviour.ALWAYS_SHOW || embed == CoreH5PDisplayOptionBehaviour.NEVER_SHOW) { + displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED] = (embed == CoreH5PDisplayOptionBehaviour.ALWAYS_SHOW); + } + + if (this.getOption(CoreH5PProvider.DISPLAY_OPTION_FRAME, true) == false) { + displayOptions[CoreH5PProvider.DISPLAY_OPTION_FRAME] = false; + } else { + displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED] = this.setDisplayOptionOverrides( + CoreH5PProvider.DISPLAY_OPTION_EMBED, CoreH5PPermission.EMBED_H5P, id, + displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED]); + + if (this.getOption(CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT, true) == false) { + displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPYRIGHT] = false; + } + } + + displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPY] = this.hasPermission(CoreH5PPermission.COPY_H5P, id); + + return displayOptions; + } } /** @@ -2633,7 +2684,7 @@ export type CoreH5PContentDBData = { id: number; // The id of the content. jsoncontent: string; // The content in json format. mainlibraryid: number; // The library we first instantiate for this node. - displayoptions: number; // H5P Button display options. + displayoptions: number; // H5P Button display options. Not used right now. foldername: string; // Name of the folder that contains the contents. fileurl: string; // The online URL of the H5P package. filtered: string; // Filtered version of json_content. diff --git a/src/providers/utils/url.ts b/src/providers/utils/url.ts index d2e9e0d47..c04240a6c 100644 --- a/src/providers/utils/url.ts +++ b/src/providers/utils/url.ts @@ -50,13 +50,19 @@ export class CoreUrlUtilsProvider { * @param url URL to add the params to. * @param params Object with the params to add. * @param anchor Anchor text if needed. + * @param boolToNumber Whether to convert bools to 1 or 0. * @return URL with params. */ - addParamsToUrl(url: string, params?: {[key: string]: any}, anchor?: string): string { + addParamsToUrl(url: string, params?: {[key: string]: any}, anchor?: string, boolToNumber?: boolean): string { let separator = url.indexOf('?') != -1 ? '&' : '?'; for (const key in params) { - const value = params[key]; + let value = params[key]; + + if (boolToNumber && typeof value == 'boolean') { + // Convert booleans to 1 or 0. + value = value ? 1 : 0; + } // Ignore objects. if (typeof value != 'object') {