MOBILE-2333 siteplugins: Support CSS files

main
Dani Palou 2018-05-02 14:52:19 +02:00
parent 28dc1232ad
commit eff08198e1
4 changed files with 205 additions and 83 deletions

View File

@ -18,8 +18,6 @@ import { CoreFileProvider } from '@providers/file';
import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreLoggerProvider } from '@providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreConstants } from '@core/constants'; import { CoreConstants } from '@core/constants';
import { Md5 } from 'ts-md5/dist/md5'; import { Md5 } from 'ts-md5/dist/md5';
@ -36,8 +34,7 @@ export class AddonRemoteThemesProvider {
protected stylesEls: {[siteId: string]: {element: HTMLStyleElement, hash: string}} = {}; protected stylesEls: {[siteId: string]: {element: HTMLStyleElement, hash: string}} = {};
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private fileProvider: CoreFileProvider, constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private fileProvider: CoreFileProvider,
private filepoolProvider: CoreFilepoolProvider, private http: Http, private utils: CoreUtilsProvider, private filepoolProvider: CoreFilepoolProvider, private http: Http, private utils: CoreUtilsProvider) {
private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider) {
this.logger = logger.getInstance('AddonRemoteThemesProvider'); this.logger = logger.getInstance('AddonRemoteThemesProvider');
} }
@ -225,7 +222,8 @@ export class AddonRemoteThemesProvider {
} }
// Styles have been loaded, now treat the CSS. // Styles have been loaded, now treat the CSS.
this.treatCSSCode(siteId, data.fileUrl, data.styles).catch(() => { this.filepoolProvider.treatCSSCode(siteId, data.fileUrl, data.styles, AddonRemoteThemesProvider.COMPONENT, 2)
.catch(() => {
// Ignore errors. // Ignore errors.
}); });
}); });
@ -304,56 +302,6 @@ export class AddonRemoteThemesProvider {
} }
} }
/**
* Search for files in a CSS code and try to download them. Once downloaded, replace their URLs
* and store the result in the CSS file.
*
* @param {string} siteId Site ID.
* @param {string} fileUrl CSS file URL.
* @param {string} cssCode CSS code.
* @return {Promise<string>} Promise resolved with the CSS code.
*/
protected treatCSSCode(siteId: string, fileUrl: string, cssCode: string): Promise<string> {
if (!this.fileProvider.isAvailable()) {
return Promise.reject(null);
}
const urls = this.domUtils.extractUrlsFromCSS(cssCode),
promises = [];
let filePath,
updated = false;
// Get the path of the CSS file.
promises.push(this.filepoolProvider.getFilePathByUrl(siteId, fileUrl).then((path) => {
filePath = path;
}));
urls.forEach((url) => {
// Download the file only if it's an online URL.
if (url.indexOf('http') == 0) {
promises.push(this.filepoolProvider.downloadUrl(siteId, url, false, AddonRemoteThemesProvider.COMPONENT, 2)
.then((fileUrl) => {
if (fileUrl != url) {
cssCode = cssCode.replace(new RegExp(this.textUtils.escapeForRegex(url), 'g'), fileUrl);
updated = true;
}
}).catch((error) => {
// It shouldn't happen. Ignore errors.
this.logger.warn('Error treating file ', url, error);
}));
}
});
return Promise.all(promises).then(() => {
// All files downloaded. Store the result if it has changed.
if (updated) {
return this.fileProvider.writeFile(filePath, cssCode);
}
}).then(() => {
return cssCode;
});
}
/** /**
* Unload styles for a temporary site. * Unload styles for a temporary site.
*/ */

View File

@ -13,12 +13,15 @@
// limitations under the License. // limitations under the License.
import { Injectable, Injector } from '@angular/core'; import { Injectable, Injector } from '@angular/core';
import { Http } from '@angular/http';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreLangProvider } from '@providers/lang'; import { CoreLangProvider } from '@providers/lang';
import { CoreLoggerProvider } from '@providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreSite } from '@classes/site'; import { CoreSite } from '@classes/site';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSitePluginsProvider } from './siteplugins'; import { CoreSitePluginsProvider } from './siteplugins';
import { CoreCompileProvider } from '@core/compile/providers/compile'; import { CoreCompileProvider } from '@core/compile/providers/compile';
@ -56,12 +59,12 @@ export class CoreSitePluginsHelperProvider {
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private injector: Injector, constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private injector: Injector,
private mainMenuDelegate: CoreMainMenuDelegate, private moduleDelegate: CoreCourseModuleDelegate, private mainMenuDelegate: CoreMainMenuDelegate, private moduleDelegate: CoreCourseModuleDelegate,
private userDelegate: CoreUserDelegate, private langProvider: CoreLangProvider, private userDelegate: CoreUserDelegate, private langProvider: CoreLangProvider, private http: Http,
private sitePluginsProvider: CoreSitePluginsProvider, private prefetchDelegate: CoreCourseModulePrefetchDelegate, private sitePluginsProvider: CoreSitePluginsProvider, private prefetchDelegate: CoreCourseModulePrefetchDelegate,
private compileProvider: CoreCompileProvider, private utils: CoreUtilsProvider, private compileProvider: CoreCompileProvider, private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider,
private courseOptionsDelegate: CoreCourseOptionsDelegate, eventsProvider: CoreEventsProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate, eventsProvider: CoreEventsProvider,
private courseFormatDelegate: CoreCourseFormatDelegate, private profileFieldDelegate: CoreUserProfileFieldDelegate, private courseFormatDelegate: CoreCourseFormatDelegate, private profileFieldDelegate: CoreUserProfileFieldDelegate,
private textUtils: CoreTextUtilsProvider) { private textUtils: CoreTextUtilsProvider, private filepoolProvider: CoreFilepoolProvider) {
this.logger = logger.getInstance('CoreSitePluginsHelperProvider'); this.logger = logger.getInstance('CoreSitePluginsHelperProvider');
// Fetch the plugins on login. // Fetch the plugins on login.
@ -88,6 +91,64 @@ export class CoreSitePluginsHelperProvider {
}); });
} }
/**
* Download the styles for a handler (if any).
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @param {string} [siteId] Site ID. If not provided, current site.
* @return {Promise<string>} Promise resolved with the CSS code.
*/
downloadStyles(plugin: any, handlerName: string, handlerSchema: any, siteId?: string): Promise<string> {
return this.sitesProvider.getSite(siteId).then((site) => {
// Get the absolute URL. If it's a relative URL, add the site URL to it.
let url = handlerSchema.styles && handlerSchema.styles.url;
if (url && !this.urlUtils.isAbsoluteURL(url)) {
url = this.textUtils.concatenatePaths(site.getURL(), url);
}
const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName),
componentId = uniqueName + '#main';
// Remove the CSS files for this handler that aren't used anymore. Don't block the call for this.
this.filepoolProvider.getFilesByComponent(site.id, CoreSitePluginsProvider.COMPONENT, componentId).then((files) => {
files.forEach((file) => {
if (file.url != url) {
// It's not the current file, delete it.
this.filepoolProvider.removeFileByUrl(site.id, file.url).catch(() => {
// Ignore errors.
});
}
});
}).catch(() => {
// Ignore errors.
});
if (!url) {
// No styles.
return '';
}
// Download the file if not downloaded or the version changed.
return this.filepoolProvider.downloadUrl(site.id, url, false, CoreSitePluginsProvider.COMPONENT, componentId, 0,
undefined, undefined, undefined, handlerSchema.styles.version).then((url) => {
// File is downloaded, get the contents.
return this.http.get(url).toPromise();
}).then((response): any => {
const text = response && response.text();
if (typeof text == 'string') {
return text;
} else {
return Promise.reject(null);
}
});
});
}
/** /**
* Execute a handler's init method if it has any. * Execute a handler's init method if it has any.
* *
@ -284,6 +345,35 @@ export class CoreSitePluginsHelperProvider {
return this.utils.allPromises(promises); return this.utils.allPromises(promises);
} }
/**
* Load the styles for a handler.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {string} fileUrl CSS file URL.
* @param {string} cssCode CSS code.
* @param {number} [version] Styles version.
* @param {string} [siteId] Site ID. If not provided, current site.
*/
loadStyles(plugin: any, handlerName: string, fileUrl: string, cssCode: string, version?: number, siteId?: string): void {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
// Create the style and add it to the header.
const styleEl = document.createElement('style'),
uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName);
styleEl.setAttribute('id', 'siteplugin-' + uniqueName);
styleEl.innerHTML = cssCode;
document.head.appendChild(styleEl);
// Styles have been loaded, now treat the CSS.
this.filepoolProvider.treatCSSCode(siteId, fileUrl, cssCode, CoreSitePluginsProvider.COMPONENT, uniqueName, version)
.catch(() => {
// Ignore errors.
});
}
/** /**
* Register a site plugin handler in the right delegate. * Register a site plugin handler in the right delegate.
* *
@ -294,8 +384,28 @@ export class CoreSitePluginsHelperProvider {
*/ */
registerHandler(plugin: any, handlerName: string, handlerSchema: any): Promise<any> { registerHandler(plugin: any, handlerName: string, handlerSchema: any): Promise<any> {
// Wait for the init JS to be executed. // Wait for the init JS to be executed and for the styles to be downloaded.
return this.executeHandlerInit(plugin, handlerSchema).then((result) => { const promises = [],
siteId = this.sitesProvider.getCurrentSiteId();
let result,
cssCode;
promises.push(this.downloadStyles(plugin, handlerName, handlerSchema, siteId).then((code) => {
cssCode = code;
}).catch((error) => {
this.logger.error('Error getting styles for plugin', handlerName, handlerSchema, error);
}));
promises.push(this.executeHandlerInit(plugin, handlerSchema).then((initResult) => {
result = initResult;
}));
return Promise.all(promises).then(() => {
if (cssCode) {
// Load the styles.
this.loadStyles(plugin, handlerName, handlerSchema.styles.url, cssCode, handlerSchema.styles.version, siteId);
}
let promise; let promise;
switch (handlerSchema.delegate) { switch (handlerSchema.delegate) {

View File

@ -58,6 +58,8 @@ export interface CoreSitePluginsHandler {
*/ */
@Injectable() @Injectable()
export class CoreSitePluginsProvider { export class CoreSitePluginsProvider {
static COMPONENT = 'CoreSitePlugins';
protected ROOT_CACHE_KEY = 'CoreSitePlugins:'; protected ROOT_CACHE_KEY = 'CoreSitePlugins:';
protected logger; protected logger;

View File

@ -22,6 +22,7 @@ import { CoreLoggerProvider } from './logger';
import { CorePluginFileDelegate } from './plugin-file-delegate'; import { CorePluginFileDelegate } from './plugin-file-delegate';
import { CoreSitesProvider } from './sites'; import { CoreSitesProvider } from './sites';
import { CoreWSProvider } from './ws'; import { CoreWSProvider } from './ws';
import { CoreDomUtilsProvider } from './utils/dom';
import { CoreMimetypeUtilsProvider } from './utils/mimetype'; import { CoreMimetypeUtilsProvider } from './utils/mimetype';
import { CoreTextUtilsProvider } from './utils/text'; import { CoreTextUtilsProvider } from './utils/text';
import { CoreTimeUtilsProvider } from './utils/time'; import { CoreTimeUtilsProvider } from './utils/time';
@ -435,7 +436,7 @@ export class CoreFilepoolProvider {
private sitesProvider: CoreSitesProvider, private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider, private sitesProvider: CoreSitesProvider, private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider,
private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, private urlUtils: CoreUrlUtilsProvider,
private timeUtils: CoreTimeUtilsProvider, private eventsProvider: CoreEventsProvider, initDelegate: CoreInitDelegate, private timeUtils: CoreTimeUtilsProvider, private eventsProvider: CoreEventsProvider, initDelegate: CoreInitDelegate,
network: Network, private pluginFileDelegate: CorePluginFileDelegate) { network: Network, private pluginFileDelegate: CorePluginFileDelegate, private domUtils: CoreDomUtilsProvider) {
this.logger = logger.getInstance('CoreFilepoolProvider'); this.logger = logger.getInstance('CoreFilepoolProvider');
this.appDB = this.appProvider.getDB(); this.appDB = this.appProvider.getDB();
@ -625,13 +626,14 @@ export class CoreFilepoolProvider {
* @param {Function} [onProgress] Function to call on progress. * @param {Function} [onProgress] Function to call on progress.
* @param {number} [priority=0] The priority this file should get in the queue (range 0-999). * @param {number} [priority=0] The priority this file should get in the queue (range 0-999).
* @param {any} [options] Extra options (isexternalfile, repositorytype). * @param {any} [options] Extra options (isexternalfile, repositorytype).
* @param {number} [revision] File revision. If not defined, it will be calculated using the URL.
* @return {Promise} Resolved on success. * @return {Promise} Resolved on success.
*/ */
addToQueueByUrl(siteId: string, fileUrl: string, component?: string, componentId?: string | number, timemodified: number = 0, addToQueueByUrl(siteId: string, fileUrl: string, component?: string, componentId?: string | number, timemodified: number = 0,
filePath?: string, onProgress?: (event: any) => any, priority: number = 0, options: any = {}): Promise<any> { filePath?: string, onProgress?: (event: any) => any, priority: number = 0, options: any = {}, revision?: number)
: Promise<any> {
let fileId, let fileId,
link, link,
revision,
queueDeferred; queueDeferred;
if (!this.fileProvider.isAvailable()) { if (!this.fileProvider.isAvailable()) {
@ -646,7 +648,7 @@ export class CoreFilepoolProvider {
return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => {
const primaryKey = { siteId: siteId, fileId: fileId }; const primaryKey = { siteId: siteId, fileId: fileId };
revision = this.getRevisionFromUrl(fileUrl); revision = revision || this.getRevisionFromUrl(fileUrl);
fileId = this.getFileIdByUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl);
// Set up the component. // Set up the component.
@ -747,10 +749,12 @@ export class CoreFilepoolProvider {
* @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise.
* Ignored if checkSize=false. * Ignored if checkSize=false.
* @param {any} [options] Extra options (isexternalfile, repositorytype). * @param {any} [options] Extra options (isexternalfile, repositorytype).
* @param {number} [revision] File revision. If not defined, it will be calculated using the URL.
* @return {Promise<any>} Promise resolved when the file is downloaded. * @return {Promise<any>} Promise resolved when the file is downloaded.
*/ */
protected addToQueueIfNeeded(siteId: string, fileUrl: string, component: string, componentId?: string | number, protected addToQueueIfNeeded(siteId: string, fileUrl: string, component: string, componentId?: string | number,
timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}): Promise<any> { timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}, revision?: number)
: Promise<any> {
let promise; let promise;
if (checkSize) { if (checkSize) {
@ -779,16 +783,17 @@ export class CoreFilepoolProvider {
if (sizeUnknown) { if (sizeUnknown) {
if (downloadUnknown && isWifi) { if (downloadUnknown && isWifi) {
return this.addToQueueByUrl( return this.addToQueueByUrl(
siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options); siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, revision);
} }
} else if (size <= this.DOWNLOAD_THRESHOLD || (isWifi && size <= this.WIFI_DOWNLOAD_THRESHOLD)) { } else if (size <= this.DOWNLOAD_THRESHOLD || (isWifi && size <= this.WIFI_DOWNLOAD_THRESHOLD)) {
return this.addToQueueByUrl( return this.addToQueueByUrl(
siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options); siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, revision);
} }
}); });
} else { } else {
// No need to check size, just add it to the queue. // No need to check size, just add it to the queue.
return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options); return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options,
revision);
} }
} }
@ -1155,6 +1160,7 @@ export class CoreFilepoolProvider {
* @param {number} [timemodified=0] The time this file was modified. Can be used to check file state. * @param {number} [timemodified=0] The time this file was modified. Can be used to check file state.
* @param {string} [filePath] Filepath to download the file to. If not defined, download to the filepool folder. * @param {string} [filePath] Filepath to download the file to. If not defined, download to the filepool folder.
* @param {any} [options] Extra options (isexternalfile, repositorytype). * @param {any} [options] Extra options (isexternalfile, repositorytype).
* @param {number} [revision] File revision. If not defined, it will be calculated using the URL.
* @return {Promise<any>} Resolved with internal URL on success, rejected otherwise. * @return {Promise<any>} Resolved with internal URL on success, rejected otherwise.
* @description * @description
* Downloads a file on the spot. * Downloads a file on the spot.
@ -1164,7 +1170,8 @@ export class CoreFilepoolProvider {
* invalidateFileByUrl to trigger a download. * invalidateFileByUrl to trigger a download.
*/ */
downloadUrl(siteId: string, fileUrl: string, ignoreStale?: boolean, component?: string, componentId?: string | number, downloadUrl(siteId: string, fileUrl: string, ignoreStale?: boolean, component?: string, componentId?: string | number,
timemodified: number = 0, onProgress?: (event: any) => any, filePath?: string, options: any = {}): Promise<any> { timemodified: number = 0, onProgress?: (event: any) => any, filePath?: string, options: any = {}, revision?: number)
: Promise<any> {
let fileId, let fileId,
promise; promise;
@ -1174,7 +1181,7 @@ export class CoreFilepoolProvider {
options = Object.assign({}, options); // Create a copy to prevent modifying the original object. options = Object.assign({}, options); // Create a copy to prevent modifying the original object.
options.timemodified = timemodified || 0; options.timemodified = timemodified || 0;
options.revision = this.getRevisionFromUrl(fileUrl); options.revision = revision || this.getRevisionFromUrl(fileUrl);
fileId = this.getFileIdByUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl);
return this.hasFileInPool(siteId, fileId).then((fileObject) => { return this.hasFileInPool(siteId, fileId).then((fileObject) => {
@ -1585,15 +1592,16 @@ export class CoreFilepoolProvider {
* @param {string} fileUrl File URL. * @param {string} fileUrl File URL.
* @param {number} [timemodified=0] The time this file was modified. * @param {number} [timemodified=0] The time this file was modified.
* @param {string} [filePath] Filepath to download the file to. If defined, no extension will be added. * @param {string} [filePath] Filepath to download the file to. If defined, no extension will be added.
* @param {number} [revision] File revision. If not defined, it will be calculated using the URL.
* @return {Promise<string>} Promise resolved with the file state. * @return {Promise<string>} Promise resolved with the file state.
*/ */
getFileStateByUrl(siteId: string, fileUrl: string, timemodified: number = 0, filePath?: string): Promise<string> { getFileStateByUrl(siteId: string, fileUrl: string, timemodified: number = 0, filePath?: string, revision?: number)
let fileId, : Promise<string> {
revision; let fileId;
return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => {
fileUrl = fixedUrl; fileUrl = fixedUrl;
revision = this.getRevisionFromUrl(fileUrl); revision = revision || this.getRevisionFromUrl(fileUrl);
fileId = this.getFileIdByUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl);
// Check if the file is in queue (waiting to be downloaded). // Check if the file is in queue (waiting to be downloaded).
@ -1638,6 +1646,7 @@ export class CoreFilepoolProvider {
* @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise.
* Ignored if checkSize=false. * Ignored if checkSize=false.
* @param {any} [options] Extra options (isexternalfile, repositorytype). * @param {any} [options] Extra options (isexternalfile, repositorytype).
* @param {number} [revision] File revision. If not defined, it will be calculated using the URL.
* @return {Promise<string>} Resolved with the URL to use. * @return {Promise<string>} Resolved with the URL to use.
* @description * @description
* This will return a URL pointing to the content of the requested URL. * This will return a URL pointing to the content of the requested URL.
@ -1647,21 +1656,20 @@ export class CoreFilepoolProvider {
*/ */
protected getFileUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, protected getFileUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number,
mode: string = 'url', timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean, mode: string = 'url', timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean,
options: any = {}): Promise<string> { options: any = {}, revision?: number): Promise<string> {
let fileId, let fileId;
revision;
const addToQueue = (fileUrl): void => { const addToQueue = (fileUrl): void => {
// Add the file to queue if needed and ignore errors. // Add the file to queue if needed and ignore errors.
this.addToQueueIfNeeded(siteId, fileUrl, component, componentId, timemodified, checkSize, this.addToQueueIfNeeded(siteId, fileUrl, component, componentId, timemodified, checkSize,
downloadUnknown, options).catch(() => { downloadUnknown, options, revision).catch(() => {
// Ignore errors. // Ignore errors.
}); });
}; };
return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => {
fileUrl = fixedUrl; fileUrl = fixedUrl;
revision = this.getRevisionFromUrl(fileUrl); revision = revision || this.getRevisionFromUrl(fileUrl);
fileId = this.getFileIdByUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl);
return this.hasFileInPool(siteId, fileId).then((entry) => { return this.hasFileInPool(siteId, fileId).then((entry) => {
@ -2083,15 +2091,16 @@ export class CoreFilepoolProvider {
* @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise.
* Ignored if checkSize=false. * Ignored if checkSize=false.
* @param {any} [options] Extra options (isexternalfile, repositorytype). * @param {any} [options] Extra options (isexternalfile, repositorytype).
* @param {number} [revision] File revision. If not defined, it will be calculated using the URL.
* @return {Promise<string>} Resolved with the URL to use. * @return {Promise<string>} Resolved with the URL to use.
* @description * @description
* This will return a URL pointing to the content of the requested URL. * This will return a URL pointing to the content of the requested URL.
* The URL returned is compatible to use with IMG tags. * The URL returned is compatible to use with IMG tags.
*/ */
getSrcByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, timemodified: number = 0, getSrcByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, timemodified: number = 0,
checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}): Promise<string> { checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}, revision?: number): Promise<string> {
return this.getFileUrlByUrl(siteId, fileUrl, component, componentId, 'src', return this.getFileUrlByUrl(siteId, fileUrl, component, componentId, 'src',
timemodified, checkSize, downloadUnknown, options); timemodified, checkSize, downloadUnknown, options, revision);
} }
/** /**
@ -2125,15 +2134,16 @@ export class CoreFilepoolProvider {
* @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise.
* Ignored if checkSize=false. * Ignored if checkSize=false.
* @param {any} [options] Extra options (isexternalfile, repositorytype). * @param {any} [options] Extra options (isexternalfile, repositorytype).
* @param {number} [revision] File revision. If not defined, it will be calculated using the URL.
* @return {Promise<string>} Resolved with the URL to use. * @return {Promise<string>} Resolved with the URL to use.
* @description * @description
* This will return a URL pointing to the content of the requested URL. * This will return a URL pointing to the content of the requested URL.
* The URL returned is compatible to use with a local browser. * The URL returned is compatible to use with a local browser.
*/ */
getUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, timemodified: number = 0, getUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, timemodified: number = 0,
checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}): Promise<string> { checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}, revision?: number): Promise<string> {
return this.getFileUrlByUrl(siteId, fileUrl, component, componentId, 'url', return this.getFileUrlByUrl(siteId, fileUrl, component, componentId, 'url',
timemodified, checkSize, downloadUnknown, options); timemodified, checkSize, downloadUnknown, options, revision);
} }
/** /**
@ -2803,6 +2813,58 @@ export class CoreFilepoolProvider {
}); });
} }
/**
* Search for files in a CSS code and try to download them. Once downloaded, replace their URLs
* and store the result in the CSS file.
*
* @param {string} siteId Site ID.
* @param {string} fileUrl CSS file URL.
* @param {string} cssCode CSS code.
* @param {string} [component] The component to link the file to.
* @param {string|number} [componentId] An ID to use in conjunction with the component.
* @param {number} [revision] Revision to use in all files. If not defined, it will be calculated using the URL of each file.
* @return {Promise<string>} Promise resolved with the CSS code.
*/
treatCSSCode(siteId: string, fileUrl: string, cssCode: string, component?: string, componentId?: string | number,
revision?: number): Promise<string> {
const urls = this.domUtils.extractUrlsFromCSS(cssCode),
promises = [];
let filePath,
updated = false;
// Get the path of the CSS file.
promises.push(this.getFilePathByUrl(siteId, fileUrl).then((path) => {
filePath = path;
}));
urls.forEach((url) => {
// Download the file only if it's an online URL.
if (url.indexOf('http') == 0) {
promises.push(this.downloadUrl(siteId, url, false, component, componentId, 0, undefined, undefined, undefined,
revision).then((fileUrl) => {
if (fileUrl != url) {
cssCode = cssCode.replace(new RegExp(this.textUtils.escapeForRegex(url), 'g'), fileUrl);
updated = true;
}
}).catch((error) => {
// It shouldn't happen. Ignore errors.
this.logger.warn('Error treating file ', url, error);
}));
}
});
return Promise.all(promises).then(() => {
// All files downloaded. Store the result if it has changed.
if (updated) {
return this.fileProvider.writeFile(filePath, cssCode);
}
}).then(() => {
return cssCode;
});
}
/** /**
* Remove extension from fileId in queue, used to migrate from previous file handling. * Remove extension from fileId in queue, used to migrate from previous file handling.
* *