From 37f8eca9e4ebd457cc38ea1f362718870ab1936f Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 3 Nov 2021 12:59:00 +0100 Subject: [PATCH] MOBILE-3896 file: Allow downloading tokenpluginfile linked files --- .../qtype/ddmarker/component/ddmarker.ts | 5 ++-- src/core/classes/site.ts | 29 +++++++++++++++++++ src/core/directives/external-content.ts | 13 +++++---- src/core/directives/tests/format-text.test.ts | 1 + .../rich-text-editor/rich-text-editor.ts | 6 ++-- .../question/services/question-helper.ts | 8 ++--- src/core/services/utils/url.ts | 14 +++++++-- 7 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/addons/qtype/ddmarker/component/ddmarker.ts b/src/addons/qtype/ddmarker/component/ddmarker.ts index cc8611b95..547773271 100644 --- a/src/addons/qtype/ddmarker/component/ddmarker.ts +++ b/src/addons/qtype/ddmarker/component/ddmarker.ts @@ -19,7 +19,6 @@ import { CoreQuestionHelper } from '@features/question/services/question-helper' import { CoreFilepool } from '@services/filepool'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreUrlUtils } from '@services/utils/url'; import { AddonQtypeDdMarkerQuestion } from '../classes/ddmarker'; /** @@ -142,9 +141,9 @@ export class AddonQtypeDdMarkerComponent extends CoreQuestionBaseComponent imple let imgSrc = this.imgSrc; const site = CoreSites.getCurrentSite(); - if (this.imgSrc && site?.canDownloadFiles() && CoreUrlUtils.isPluginFileUrl(this.imgSrc)) { + if (this.imgSrc && site?.canDownloadFiles() && site.isSitePluginFileUrl(this.imgSrc)) { imgSrc = await CoreFilepool.getSrcByUrl( - site.id!, + site.getId(), this.imgSrc, this.component, this.componentId, diff --git a/src/core/classes/site.ts b/src/core/classes/site.ts index 3d7a70130..b9b9f0191 100644 --- a/src/core/classes/site.ts +++ b/src/core/classes/site.ts @@ -1893,6 +1893,35 @@ export class CoreSite { return this.tokenPluginFileWorksPromise; } + /** + * Check if a URL to a file belongs to the site and uses the pluginfileurl or tokenpluginfileurl endpoints. + * + * @param url File URL to check. + * @return Whether it's a site file URL. + */ + isSitePluginFileUrl(url: string): boolean { + const isPluginFileUrl = CoreUrlUtils.isPluginFileUrl(url) || CoreUrlUtils.isTokenPluginFileUrl(url); + if (!isPluginFileUrl) { + return false; + } + + return this.containsUrl(url); + } + + /** + * Check if a URL to a file belongs to the site and is a theme image file. + * + * @param url File URL to check. + * @return Whether it's a site theme image URL. + */ + isSiteThemeImageUrl(url: string): boolean { + if (!CoreUrlUtils.isThemeImageUrl(url)) { + return false; + } + + return this.containsUrl(url); + } + } /** diff --git a/src/core/directives/external-content.ts b/src/core/directives/external-content.ts index d3e157dcc..efe74f020 100644 --- a/src/core/directives/external-content.ts +++ b/src/core/directives/external-content.ts @@ -213,8 +213,11 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges { } + const site = await CoreSites.getSite(siteId); + const isSiteFile = site.isSitePluginFileUrl(url); + if (!url || !url.match(/^https?:\/\//i) || CoreUrlUtils.isLocalFileUrl(url) || - (tagName === 'A' && !CoreUrlUtils.isDownloadableUrl(url))) { + (tagName === 'A' && !(isSiteFile || site.isSiteThemeImageUrl(url) || CoreUrlUtils.isGravatarUrl(url)))) { this.logger.debug('Ignoring non-downloadable URL: ' + url); if (tagName === 'SOURCE') { @@ -225,9 +228,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges { throw new CoreError('Non-downloadable URL'); } - const site = await CoreSites.getSite(siteId); - - if (!site.canDownloadFiles() && CoreUrlUtils.isPluginFileUrl(url)) { + if (!site.canDownloadFiles() && isSiteFile) { this.element.parentElement?.removeChild(this.element); // Remove element since it'll be broken. throw new CoreError('Site doesn\'t allow downloading files.'); @@ -329,7 +330,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges { return; } - let inlineStyles = this.element.getAttribute('style'); + let inlineStyles = this.element.getAttribute('style') || ''; if (!inlineStyles) { return; @@ -346,7 +347,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges { const finalUrl = await CoreFilepool.getUrlByUrl(siteId, url, this.component, this.componentId, 0, true, true); this.logger.debug('Using URL ' + finalUrl + ' for ' + url + ' in inline styles'); - inlineStyles = inlineStyles!.replace(new RegExp(url, 'gi'), finalUrl); + inlineStyles = inlineStyles.replace(new RegExp(url, 'gi'), finalUrl); }); try { diff --git a/src/core/directives/tests/format-text.test.ts b/src/core/directives/tests/format-text.test.ts index aea7a5ded..f55b01f99 100644 --- a/src/core/directives/tests/format-text.test.ts +++ b/src/core/directives/tests/format-text.test.ts @@ -125,6 +125,7 @@ describe('CoreFormatTextDirective', () => { getId: () => '42', canDownloadFiles: () => true, isVersionGreaterEqualThan: () => true, + isSitePluginFileUrl: () => false, }); // @todo this is done because we cannot mock image being loaded, we should find an alternative... diff --git a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts index 7a4658217..84c2931ef 100644 --- a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts +++ b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts @@ -550,8 +550,9 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn } const elements = Array.from(this.editorElement.querySelectorAll('img')); + const site = CoreSites.getCurrentSite(); const siteId = CoreSites.getCurrentSiteId(); - const canDownloadFiles = CoreSites.getCurrentSite()!.canDownloadFiles(); + const canDownloadFiles = !site || site.canDownloadFiles(); elements.forEach(async (el) => { if (el.getAttribute('data-original-src')) { // Already treated. @@ -560,8 +561,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn const url = el.src; - if (!url || !CoreUrlUtils.isDownloadableUrl(url) || - (!canDownloadFiles && CoreUrlUtils.isPluginFileUrl(url))) { + if (!url || !CoreUrlUtils.isDownloadableUrl(url) || (!canDownloadFiles && site?.isSitePluginFileUrl(url))) { // Nothing to treat. return; } diff --git a/src/core/features/question/services/question-helper.ts b/src/core/features/question/services/question-helper.ts index f727c2264..6aacaca8a 100644 --- a/src/core/features/question/services/question-helper.ts +++ b/src/core/features/question/services/question-helper.ts @@ -13,6 +13,7 @@ // limitations under the License. import { Injectable, EventEmitter } from '@angular/core'; +import { FileEntry, DirectoryEntry } from '@ionic-native/file/ngx'; import { CoreFile } from '@services/file'; import { CoreFileHelper } from '@services/file-helper'; @@ -20,7 +21,6 @@ import { CoreFilepool } from '@services/filepool'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; -import { CoreUrlUtils } from '@services/utils/url'; import { CoreUtils } from '@services/utils/utils'; import { CoreWSFile } from '@services/ws'; import { makeSingleton, Translate } from '@singletons'; @@ -138,7 +138,7 @@ export class CoreQuestionHelperProvider { // Search the radio button inside this certainty and add its data to the options array. const input = label.querySelector('input[type="radio"]'); if (input) { - question.behaviourCertaintyOptions!.push({ + question.behaviourCertaintyOptions?.push({ id: input.id, name: input.name, value: input.value, @@ -650,7 +650,7 @@ export class CoreQuestionHelperProvider { } treated[fileUrl] = true; - if (!site.canDownloadFiles() && CoreUrlUtils.isPluginFileUrl(fileUrl)) { + if (!site.canDownloadFiles() && site.isSitePluginFileUrl(fileUrl)) { return; } @@ -791,7 +791,7 @@ export class CoreQuestionHelperProvider { } // Replace the icon with the font version. - const newIcon: HTMLElement = document.createElement('ion-icon'); + const newIcon: HTMLIonIconElement = document.createElement('ion-icon'); if (correct) { newIcon.setAttribute('name', 'fas-check'); diff --git a/src/core/services/utils/url.ts b/src/core/services/utils/url.ts index 79aafb51c..1fbf79211 100644 --- a/src/core/services/utils/url.ts +++ b/src/core/services/utils/url.ts @@ -409,7 +409,7 @@ export class CoreUrlUtilsProvider { * @return Whether the URL is downloadable. */ isDownloadableUrl(url: string): boolean { - return this.isPluginFileUrl(url) || this.isThemeImageUrl(url) || this.isGravatarUrl(url); + return this.isPluginFileUrl(url) || this.isTokenPluginFileUrl(url) || this.isThemeImageUrl(url) || this.isGravatarUrl(url); } /** @@ -470,7 +470,17 @@ export class CoreUrlUtilsProvider { * @return Whether the URL is a pluginfile URL. */ isPluginFileUrl(url: string): boolean { - return url?.indexOf('/pluginfile.php') !== -1; + return url.indexOf('/pluginfile.php') !== -1; + } + + /** + * Returns if a URL is a tokenpluginfile URL. + * + * @param url The URL to test. + * @return Whether the URL is a tokenpluginfile URL. + */ + isTokenPluginFileUrl(url: string): boolean { + return url.indexOf('/tokenpluginfile.php') !== -1; } /**