From c4221f13679a819e7e343215a2ae62443d901eea Mon Sep 17 00:00:00 2001
From: Dani Palou <dani@moodle.com>
Date: Mon, 13 Aug 2018 16:31:24 +0200
Subject: [PATCH] MOBILE-2364 format-text: Download inline styles files

---
 src/directives/external-content.ts | 48 +++++++++++++++++++++++++++---
 src/directives/format-text.ts      | 15 ++++++++--
 2 files changed, 57 insertions(+), 6 deletions(-)

diff --git a/src/directives/external-content.ts b/src/directives/external-content.ts
index 93a20ee76..b68273ad8 100644
--- a/src/directives/external-content.ts
+++ b/src/directives/external-content.ts
@@ -20,6 +20,7 @@ import { CoreFilepoolProvider } from '@providers/filepool';
 import { CoreSitesProvider } from '@providers/sites';
 import { CoreDomUtilsProvider } from '@providers/utils/dom';
 import { CoreUrlUtilsProvider } from '@providers/utils/url';
+import { CoreUtilsProvider } from '@providers/utils/utils';
 
 /**
  * Directive to handle external content.
@@ -28,6 +29,8 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url';
  * which we want to have available when the app is offline. Typically media and links.
  *
  * If a file is downloaded, its URL will be replaced by the local file URL.
+ *
+ * From v3.5.2 this directive will also download inline styles, so it can be used in any element as long as it has inline styles.
  */
 @Directive({
     selector: '[core-external-content]'
@@ -42,7 +45,7 @@ export class CoreExternalContentDirective implements AfterViewInit {
 
     constructor(element: ElementRef, logger: CoreLoggerProvider, private filepoolProvider: CoreFilepoolProvider,
             private platform: Platform, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider,
-            private urlUtils: CoreUrlUtilsProvider, private appProvider: CoreAppProvider) {
+            private urlUtils: CoreUrlUtilsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider) {
         // This directive can be added dynamically. In that case, the first param is the HTMLElement.
         this.element = element.nativeElement || element;
         this.logger = logger.getInstance('CoreExternalContentDirective');
@@ -58,6 +61,11 @@ export class CoreExternalContentDirective implements AfterViewInit {
         let targetAttr,
             sourceAttr;
 
+        // Always handle inline styles (if any).
+        this.handleInlineStyles(siteId).catch((error) => {
+            this.logger.error('Error treating inline styles.', this.element);
+        });
+
         if (tagName === 'A') {
             targetAttr = 'href';
             sourceAttr = 'href';
@@ -81,9 +89,6 @@ export class CoreExternalContentDirective implements AfterViewInit {
             }
 
         } else {
-            // Unsupported tag.
-            this.logger.warn('Directive attached to non-supported tag: ' + tagName);
-
             return;
         }
 
@@ -217,4 +222,39 @@ export class CoreExternalContentDirective implements AfterViewInit {
             });
         });
     }
+
+    /**
+     * Handle inline styles, trying to download referenced files.
+     *
+     * @param {string} siteId Site ID.
+     * @return {Promise<any>} Promise resolved if the element is successfully treated.
+     */
+    protected handleInlineStyles(siteId: string): Promise<any> {
+        let inlineStyles = this.element.getAttribute('style');
+
+        if (!inlineStyles) {
+            return Promise.resolve();
+        }
+
+        let urls = inlineStyles.match(/https?:\/\/[^"'\) ;]*/g);
+        if (!urls || !urls.length) {
+            return Promise.resolve();
+        }
+
+        const promises = [];
+        urls = this.utils.uniqueArray(urls); // Remove duplicates.
+
+        urls.forEach((url) => {
+            promises.push(this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, true, true)
+                    .then((finalUrl) => {
+
+                this.logger.debug('Using URL ' + finalUrl + ' for ' + url + ' in inline styles');
+                inlineStyles = inlineStyles.replace(new RegExp(url, 'gi'), finalUrl);
+            }));
+        });
+
+        return this.utils.allPromises(promises).then(() => {
+            this.element.setAttribute('style', inlineStyles);
+        });
+    }
 }
diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts
index acaed8032..7efa99f04 100644
--- a/src/directives/format-text.ts
+++ b/src/directives/format-text.ts
@@ -87,7 +87,7 @@ export class CoreFormatTextDirective implements OnChanges {
     protected addExternalContent(element: HTMLElement): void {
         // Angular 2 doesn't let adding directives dynamically. Create the CoreExternalContentDirective manually.
         const extContent = new CoreExternalContentDirective(<any> element, this.loggerProvider, this.filepoolProvider,
-            this.platform, this.sitesProvider, this.domUtils, this.urlUtils, this.appProvider);
+            this.platform, this.sitesProvider, this.domUtils, this.urlUtils, this.appProvider, this.utils);
 
         extContent.component = this.component;
         extContent.componentId = this.componentId;
@@ -313,7 +313,8 @@ export class CoreFormatTextDirective implements OnChanges {
                 audios,
                 videos,
                 iframes,
-                buttons;
+                buttons,
+                elementsWithInlineStyles;
 
             div.innerHTML = formatted;
             images = Array.from(div.querySelectorAll('img'));
@@ -322,6 +323,7 @@ export class CoreFormatTextDirective implements OnChanges {
             videos = Array.from(div.querySelectorAll('video'));
             iframes = Array.from(div.querySelectorAll('iframe'));
             buttons = Array.from(div.querySelectorAll('.button'));
+            elementsWithInlineStyles = Array.from(div.querySelectorAll('*[style]'));
 
             // Walk through the content to find the links and add our directive to it.
             // Important: We need to look for links first because in 'img' we add new links without core-link.
@@ -370,6 +372,15 @@ export class CoreFormatTextDirective implements OnChanges {
                 }
             });
 
+            // Handle inline styles.
+            elementsWithInlineStyles.forEach((el: HTMLElement) => {
+                // Only add external content for tags that haven't been treated already.
+                if (el.tagName != 'A' && el.tagName != 'IMG' && el.tagName != 'AUDIO' && el.tagName != 'VIDEO'
+                        && el.tagName != 'SOURCE' && el.tagName != 'TRACK') {
+                    this.addExternalContent(el);
+                }
+            });
+
             return div;
         });
     }