MOBILE-3332 h5p: Allow disabling offline h5p

main
Dani Palou 2020-02-06 15:06:10 +01:00
parent 67da1cdeba
commit 9dcd84c903
12 changed files with 244 additions and 48 deletions

View File

@ -1619,6 +1619,7 @@
"core.h5p.offlineDialogRetryButtonLabel": "h5p",
"core.h5p.offlineDialogRetryMessage": "h5p",
"core.h5p.offlineSuccessfulSubmit": "h5p",
"core.h5p.offlinedisabled": "local_moodlemobileapp",
"core.h5p.originator": "h5p",
"core.h5p.pd": "h5p",
"core.h5p.pddl": "h5p",

View File

@ -14,6 +14,7 @@
import { Component, Injector } from '@angular/core';
import { CoreAppProvider } from '@providers/app';
import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCourseProvider } from '@core/course/providers/course';
@ -38,10 +39,15 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
contentText: string;
displayDescription = true;
constructor(injector: Injector, private resourceProvider: AddonModResourceProvider, private courseProvider: CoreCourseProvider,
private appProvider: CoreAppProvider, private prefetchHandler: AddonModResourcePrefetchHandler,
private resourceHelper: AddonModResourceHelperProvider, private sitesProvider: CoreSitesProvider,
private utils: CoreUtilsProvider) {
constructor(injector: Injector,
protected resourceProvider: AddonModResourceProvider,
protected courseProvider: CoreCourseProvider,
protected appProvider: CoreAppProvider,
protected prefetchHandler: AddonModResourcePrefetchHandler,
protected resourceHelper: AddonModResourceHelperProvider,
protected sitesProvider: CoreSitesProvider,
protected utils: CoreUtilsProvider,
protected filepoolProvider: CoreFilepoolProvider) {
super(injector);
}
@ -147,15 +153,23 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
/**
* Opens a file.
*
* @return Promise resolved when done.
*/
open(): void {
this.prefetchHandler.isDownloadable(this.module, this.courseId).then((downloadable) => {
async open(): Promise<void> {
let downloadable = await this.prefetchHandler.isDownloadable(this.module, this.courseId);
if (downloadable) {
this.resourceHelper.openModuleFile(this.module, this.courseId);
} else {
// Check if the main file is downloadle.
// This isn't done in "isDownloadable" to prevent extra WS calls in the course page.
downloadable = await this.resourceHelper.isMainFileDownloadable(this.module);
if (downloadable) {
return this.resourceHelper.openModuleFile(this.module, this.courseId);
}
}
// The resource cannot be downloaded, open the activity in browser.
return this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(this.module.url);
}
});
}
}

View File

@ -20,6 +20,7 @@ import { AddonModResourceProvider } from './resource';
import { CoreSitesProvider } from '@providers/sites';
import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreFileProvider } from '@providers/file';
import { CoreFileHelperProvider } from '@providers/file-helper';
import { CoreAppProvider } from '@providers/app';
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -36,11 +37,17 @@ export class AddonModResourceHelperProvider {
// Display using object tag.
protected DISPLAY_EMBED = 1;
constructor(private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider,
private resourceProvider: AddonModResourceProvider, private courseHelper: CoreCourseHelperProvider,
private textUtils: CoreTextUtilsProvider, private mimetypeUtils: CoreMimetypeUtilsProvider,
private fileProvider: CoreFileProvider, private appProvider: CoreAppProvider,
private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider) {
constructor(protected courseProvider: CoreCourseProvider,
protected domUtils: CoreDomUtilsProvider,
protected resourceProvider: AddonModResourceProvider,
protected courseHelper: CoreCourseHelperProvider,
protected textUtils: CoreTextUtilsProvider,
protected mimetypeUtils: CoreMimetypeUtilsProvider,
protected fileProvider: CoreFileProvider,
protected appProvider: CoreAppProvider,
protected filepoolProvider: CoreFilepoolProvider,
protected sitesProvider: CoreSitesProvider,
protected fileHelper: CoreFileHelperProvider) {
}
/**
@ -136,6 +143,23 @@ export class AddonModResourceHelperProvider {
return mimetype == 'text/html';
}
/**
* Check if main file of resource is downloadable.
*
* @param module Module instance.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with boolean: whether main file is downloadable.
*/
isMainFileDownloadable(module: any, siteId?: string): Promise<boolean> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const mainFile = module.contents[0];
const fileUrl = this.fileHelper.getFileUrl(mainFile);
const timemodified = this.fileHelper.getFileTimemodified(mainFile);
return this.filepoolProvider.isFileDownloadable(siteId, fileUrl, timemodified);
}
/**
* Check if the resource is a Nextcloud file.
*

View File

@ -1619,6 +1619,7 @@
"core.h5p.offlineDialogRetryButtonLabel": "Retry now",
"core.h5p.offlineDialogRetryMessage": "Retrying in :num....",
"core.h5p.offlineSuccessfulSubmit": "Successfully submitted results.",
"core.h5p.offlinedisabled": "The site doesn't allow downloading H5P packages.",
"core.h5p.originator": "Originator",
"core.h5p.pd": "Public Domain",
"core.h5p.pddl": "Public Domain Dedication and Licence",

View File

@ -20,6 +20,7 @@ import { CoreFileHelperProvider } from '@providers/file-helper';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreConstants } from '@core/constants';
@ -57,16 +58,17 @@ export class CoreFileComponent implements OnInit, OnDestroy {
protected timemodified: number;
protected observer;
constructor(private sitesProvider: CoreSitesProvider,
private utils: CoreUtilsProvider,
private domUtils: CoreDomUtilsProvider,
private filepoolProvider: CoreFilepoolProvider,
private appProvider: CoreAppProvider,
private fileHelper: CoreFileHelperProvider,
private mimeUtils: CoreMimetypeUtilsProvider,
private eventsProvider: CoreEventsProvider,
private textUtils: CoreTextUtilsProvider,
private pluginFileDelegate: CorePluginFileDelegate) {
constructor(protected sitesProvider: CoreSitesProvider,
protected utils: CoreUtilsProvider,
protected domUtils: CoreDomUtilsProvider,
protected filepoolProvider: CoreFilepoolProvider,
protected appProvider: CoreAppProvider,
protected fileHelper: CoreFileHelperProvider,
protected mimeUtils: CoreMimetypeUtilsProvider,
protected eventsProvider: CoreEventsProvider,
protected textUtils: CoreTextUtilsProvider,
protected pluginFileDelegate: CorePluginFileDelegate,
protected urlUtils: CoreUrlUtilsProvider) {
this.onDelete = new EventEmitter();
}
@ -104,6 +106,8 @@ export class CoreFileComponent implements OnInit, OnDestroy {
this.observer = this.eventsProvider.on(eventName, () => {
this.calculateState();
});
}).catch(() => {
// File not downloadable.
});
}
}
@ -152,14 +156,14 @@ export class CoreFileComponent implements OnInit, OnDestroy {
return;
}
if (!this.canDownload) {
if (!this.canDownload || !this.state || this.state == CoreConstants.NOT_DOWNLOADABLE) {
// File cannot be downloaded, just open it.
if (this.file.toURL) {
// Local file.
this.utils.openFile(this.file.toURL());
} else if (this.fileUrl) {
if (this.fileUrl.indexOf('http') === 0) {
this.utils.openOnlineFile(this.fileUrl);
this.utils.openOnlineFile(this.urlUtils.unfixPluginfileURL(this.fileUrl));
} else {
this.utils.openFile(this.fileUrl);
}

View File

@ -73,7 +73,8 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy {
this.logger = loggerProvider.getInstance('CoreH5PPlayerComponent');
this.site = sitesProvider.getCurrentSite();
this.siteId = this.site.getId();
this.siteCanDownload = this.sitesProvider.getCurrentSite().canDownloadFiles();
this.siteCanDownload = this.sitesProvider.getCurrentSite().canDownloadFiles() &&
!this.h5pProvider.isOfflineDisabledInSite();
}
/**

View File

@ -64,6 +64,7 @@
"offlineDialogRetryButtonLabel": "Retry now",
"offlineDialogRetryMessage": "Retrying in :num....",
"offlineSuccessfulSubmit": "Successfully submitted results.",
"offlinedisabled": "The site doesn't allow downloading H5P packages.",
"originator": "Originator",
"pd": "Public Domain",
"pddl": "Public Domain Dedication and Licence",

View File

@ -1876,6 +1876,30 @@ export class CoreH5PProvider {
});
}
/**
* Check whether H5P offline is disabled.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with boolean: whether is disabled.
*/
async isOfflineDisabled(siteId?: string): Promise<boolean> {
const site = await this.sitesProvider.getSite(siteId);
return this.isOfflineDisabledInSite(site);
}
/**
* Check whether H5P offline is disabled.
*
* @param site Site instance. If not defined, current site.
* @return Whether is disabled.
*/
isOfflineDisabledInSite(site?: CoreSite): boolean {
site = site || this.sitesProvider.getCurrentSite();
return site.isFeatureDisabled('NoDelegate_H5POffline');
}
/**
* Performs actions required when a library has been installed.
*

View File

@ -22,6 +22,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreH5PProvider } from './h5p';
import { CoreWSExternalFile } from '@providers/ws';
import { FileEntry } from '@ionic-native/file';
import { TranslateService } from '@ngx-translate/core';
/**
* Handler to treat H5P files.
@ -35,7 +36,8 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler {
protected textUtils: CoreTextUtilsProvider,
protected utils: CoreUtilsProvider,
protected fileProvider: CoreFileProvider,
protected h5pProvider: CoreH5PProvider) { }
protected h5pProvider: CoreH5PProvider,
protected translate: TranslateService) { }
/**
* React to a file being deleted.
@ -112,6 +114,28 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler {
return this.h5pProvider.canGetTrustedH5PFileInSite();
}
/**
* Check if a file is downloadable.
*
* @param file The file data.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with a boolean and a reason why it isn't downloadable if needed.
*/
async isFileDownloadable(file: CoreWSExternalFile, siteId?: string): Promise<{downloadable: boolean, reason?: string}> {
const offlineDisabled = await this.h5pProvider.isOfflineDisabled(siteId);
if (offlineDisabled) {
return {
downloadable: false,
reason: this.translate.instant('core.h5p.offlinedisabled'),
};
} else {
return {
downloadable: true,
};
}
}
/**
* Check whether the file should be treated by this handler. It is used in functions where the component isn't used.
*

View File

@ -2385,6 +2385,23 @@ export class CoreFilepoolProvider {
});
}
/**
* Check whether a file is downloadable.
*
* @param siteId The site ID.
* @param fileUrl File URL.
* @param timemodified The time this file was modified.
* @param filePath Filepath to download the file to. If defined, no extension will be added.
* @param revision File revision. If not defined, it will be calculated using the URL.
* @return Promise resolved with a boolean: whether a file is downloadable.
*/
async isFileDownloadable(siteId: string, fileUrl: string, timemodified: number = 0, filePath?: string, revision?: number)
: Promise<boolean> {
const state = await this.getFileStateByUrl(siteId, fileUrl, timemodified, filePath, revision);
return state != CoreConstants.NOT_DOWNLOADABLE;
}
/**
* Check if a file is downloading.
*

View File

@ -84,6 +84,15 @@ export interface CorePluginFileHandler extends CoreDelegateHandler {
*/
getFileSize?(file: CoreWSExternalFile, siteId?: string): Promise<number>;
/**
* Check if a file is downloadable.
*
* @param file The file data.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with a boolean and a reason why it isn't downloadable if needed.
*/
isFileDownloadable?(file: CoreWSExternalFile, siteId?: string): Promise<CorePluginFileDownloadableResult>;
/**
* Check whether the file should be treated by this handler. It is used in functions where the component isn't used.
*
@ -103,6 +112,21 @@ export interface CorePluginFileHandler extends CoreDelegateHandler {
treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string): Promise<any>;
}
/**
* Data about if a file is downloadable.
*/
export type CorePluginFileDownloadableResult = {
/**
* Whether it's downloadable.
*/
downloadable: boolean;
/**
* If not downloadable, the reason why it isn't.
*/
reason?: string;
};
/**
* Delegate to register pluginfile information handlers.
*/
@ -155,16 +179,22 @@ export class CorePluginFileDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the file to use. Rejected if cannot download.
*/
protected getHandlerDownloadableFile(file: CoreWSExternalFile, handler: CorePluginFileHandler, siteId?: string)
protected async getHandlerDownloadableFile(file: CoreWSExternalFile, handler: CorePluginFileHandler, siteId?: string)
: Promise<CoreWSExternalFile> {
if (handler && handler.getDownloadableFile) {
return handler.getDownloadableFile(file, siteId).then((newFile) => {
return newFile || file;
});
const isDownloadable = await this.isFileDownloadable(file, siteId);
if (!isDownloadable.downloadable) {
throw isDownloadable.reason;
}
return Promise.resolve(file);
if (handler && handler.getDownloadableFile) {
const newFile = await handler.getDownloadableFile(file, siteId);
return newFile || file;
}
return file;
}
/**
@ -240,23 +270,32 @@ export class CorePluginFileDelegate extends CoreDelegate {
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the size.
*/
getFileSize(file: CoreWSExternalFile, siteId?: string): Promise<number> {
async getFileSize(file: CoreWSExternalFile, siteId?: string): Promise<number> {
const isDownloadable = await this.isFileDownloadable(file, siteId);
if (!isDownloadable.downloadable) {
return 0;
}
const handler = this.getHandlerForFile(file);
// First of all check if file can be downloaded.
return this.getHandlerDownloadableFile(file, handler, siteId).then((file) => {
if (!file) {
const downloadableFile = await this.getHandlerDownloadableFile(file, handler, siteId);
if (!downloadableFile) {
return 0;
}
if (handler && handler.getFileSize) {
return handler.getFileSize(file, siteId).catch(() => {
return file.filesize;
});
try {
const size = handler.getFileSize(downloadableFile, siteId);
return size;
} catch (error) {
// Ignore errors.
}
}
return Promise.resolve(file.filesize);
});
return downloadableFile.filesize;
}
/**
@ -275,6 +314,24 @@ export class CorePluginFileDelegate extends CoreDelegate {
}
}
/**
* Check if a file is downloadable.
*
* @param file The file data.
* @param siteId Site ID. If not defined, current site.
* @return Promise with the data.
*/
isFileDownloadable(file: CoreWSExternalFile, siteId?: string): Promise<CorePluginFileDownloadableResult> {
const handler = this.getHandlerForFile(file);
if (handler && handler.isFileDownloadable) {
return handler.isFileDownloadable(file, siteId);
}
// Default to true.
return Promise.resolve({downloadable: true});
}
/**
* Removes the revision number from a file URL.
*

View File

@ -469,4 +469,32 @@ export class CoreUrlUtilsProvider {
return matches && matches[0];
}
/**
* Modifies a pluginfile URL to use the default pluginfile script instead of the webservice one.
*
* @param url The url to be fixed.
* @param siteUrl The URL of the site the URL belongs to.
* @return Modified URL.
*/
unfixPluginfileURL(url: string, siteUrl?: string): string {
if (!url) {
return '';
}
url = url.replace(/&amp;/g, '&');
// It site URL is supplied, check if the URL belongs to the site.
if (siteUrl && url.indexOf(this.textUtils.addEndingSlash(siteUrl)) !== 0) {
return url;
}
// Not a pluginfile URL. Treat webservice/pluginfile case.
url = url.replace(/\/webservice\/pluginfile\.php\//, '/pluginfile.php/');
// Make sure the URL doesn't contain the token.
url.replace(/([?&])token=[^&]*&?/, '$1');
return url;
}
}