diff --git a/scripts/langindex.json b/scripts/langindex.json index ba9376e82..6279d4d95 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1403,6 +1403,7 @@ "core.more": "moodle", "core.mygroups": "group", "core.name": "moodle", + "core.networkerroriframemsg": "local_moodlemobileapp", "core.networkerrormsg": "local_moodlemobileapp", "core.never": "moodle", "core.next": "moodle", diff --git a/src/app/app.scss b/src/app/app.scss index d465feaf8..0813e34e9 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -941,6 +941,10 @@ ion-app.app-root { &.disable-scroll ion-modal .ion-page { pointer-events: initial; } + + .core-iframe-offline-disabled { + display: none !important; + } } @each $color-name, $color-base, $color-contrast in get-colors($colors) { diff --git a/src/components/icon/icon.ts b/src/components/icon/icon.ts index 8c058ea50..931a06875 100644 --- a/src/components/icon/icon.ts +++ b/src/components/icon/icon.ts @@ -66,7 +66,20 @@ export class CoreIconComponent implements OnInit { const attrs = this.element.attributes; for (let i = attrs.length - 1; i >= 0; i--) { - newElement.setAttribute(attrs[i].name, attrs[i].value); + if (attrs[i].name == 'class') { + // We don't want to override the classes we already added. Add them one by one. + if (attrs[i].value) { + const classes = attrs[i].value.split(' '); + for (let j = 0; j < classes.length; j++) { + if (classes[j]) { + newElement.classList.add(classes[j]); + } + } + } + + } else { + newElement.setAttribute(attrs[i].name, attrs[i].value); + } } this.element.parentElement.replaceChild(newElement, this.element); @@ -79,12 +92,12 @@ export class CoreIconComponent implements OnInit { * @return {boolean} If has a value equivalent to true. */ isTrueProperty(val: any): boolean { - if (typeof val === 'string') { - val = val.toLowerCase().trim(); + if (typeof val === 'string') { + val = val.toLowerCase().trim(); - return (val === 'true' || val === 'on' || val === ''); - } + return (val === 'true' || val === 'on' || val === ''); + } - return !!val; + return !!val; } } diff --git a/src/lang/en.json b/src/lang/en.json index 4e165a8b6..843476b1f 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -153,6 +153,7 @@ "mygroups": "My groups", "name": "Name", "nograde": "No grade", + "networkerroriframemsg": "This content is not available offline. Please connect to the internet and try again.", "networkerrormsg": "There was a problem connecting to the site. Please check your connection and try again.", "never": "Never", "next": "Next", diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index 4ed2f7bee..979a01980 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -429,6 +429,18 @@ export class CoreDomUtilsProvider { return parseInt(style[measure], 10) || 0; } + /** + * Get the HTML code to render a connection warning icon. + * + * @return {string} HTML Code. + */ + getConnectionWarningIconHtml(): string { + return '
' + + '' + + '' + + '
'; + } + /** * Returns width of an element. * @@ -500,12 +512,9 @@ export class CoreDomUtilsProvider { */ private getErrorTitle(message: string): any { if (message == this.translate.instant('core.networkerrormsg') || - message == this.translate.instant('core.fileuploader.errormustbeonlinetoupload')) { - return this.sanitizer.bypassSecurityTrustHtml('
' + - '' + - '' + - '
'); + message == this.translate.instant('core.fileuploader.errormustbeonlinetoupload')) { + return this.sanitizer.bypassSecurityTrustHtml(this.getConnectionWarningIconHtml()); } return this.textUtils.decodeHTML(this.translate.instant('core.error')); diff --git a/src/providers/utils/iframe.ts b/src/providers/utils/iframe.ts index 504bdb3ae..04863e1c8 100644 --- a/src/providers/utils/iframe.ts +++ b/src/providers/utils/iframe.ts @@ -12,8 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { Platform } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { Network } from '@ionic-native/network'; +import { CoreAppProvider } from '../app'; import { CoreFileProvider } from '../file'; import { CoreLoggerProvider } from '../logger'; import { CoreSitesProvider } from '../sites'; @@ -33,10 +36,75 @@ export class CoreIframeUtilsProvider { constructor(logger: CoreLoggerProvider, private fileProvider: CoreFileProvider, private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider, - private domUtils: CoreDomUtilsProvider, private platform: Platform) { + private domUtils: CoreDomUtilsProvider, private platform: Platform, private appProvider: CoreAppProvider, + private translate: TranslateService, private network: Network, private zone: NgZone) { this.logger = logger.getInstance('CoreUtilsProvider'); } + /** + * Check if a frame uses an online URL but the app is offline. If it does, the iframe is hidden and a warning is shown. + * + * @param {any} element The frame to check (iframe, embed, ...). + * @param {boolean} [isSubframe] Whether it's a frame inside another frame. + * @return {boolean} True if frame is online and the app is offline, false otherwise. + */ + checkOnlineFrameInOffline(element: any, isSubframe?: boolean): boolean { + const src = element.src || element.data; + + if (src && src.match(/^https?:\/\//i) && !this.appProvider.isOnline()) { + if (element.classList.contains('core-iframe-offline-disabled')) { + // Iframe already hidden, stop. + return true; + } + + // The frame has an online URL but the app is offline. Show a warning. + const div = document.createElement('div'); + + div.setAttribute('text-center', ''); + div.setAttribute('padding', ''); + div.classList.add('core-iframe-offline-warning'); + div.innerHTML = (isSubframe ? '' : this.domUtils.getConnectionWarningIconHtml()) + + '

' + this.translate.instant('core.networkerroriframemsg') + '

'; + + element.parentElement.insertBefore(div, element); + + // Add a class to specify that the iframe is hidden. + element.classList.add('core-iframe-offline-disabled'); + + if (isSubframe) { + // We cannot apply CSS styles in subframes, just hide the iframe. + element.style.display = 'none'; + } + + // If the network changes, check it again. + const subscription = this.network.onConnect().subscribe(() => { + // Execute the callback in the Angular zone, so change detection doesn't stop working. + this.zone.run(() => { + if (!this.checkOnlineFrameInOffline(element, isSubframe)) { + // Now the app is online, no need to check connection again. + subscription.unsubscribe(); + } + }); + }); + + return true; + } else if (element.classList.contains('core-iframe-offline-disabled')) { + // Reload the frame. + element.src = element.src; + element.data = element.data; + + // Remove the warning and show the iframe + this.domUtils.removeElement(element.parentElement, 'div.core-iframe-offline-warning'); + element.classList.remove('core-iframe-offline-disabled'); + + if (isSubframe) { + element.style.display = ''; + } + } + + return false; + } + /** * Given an element, return the content window and document. * Please notice that the element should be an iframe, embed or similar. @@ -136,7 +204,7 @@ export class CoreIframeUtilsProvider { CoreIframeUtilsProvider.FRAME_TAGS.forEach((tag) => { const elements = Array.from(contentDocument.querySelectorAll(tag)); elements.forEach((subElement) => { - this.treatFrame(subElement); + this.treatFrame(subElement, true); }); }); } @@ -147,9 +215,12 @@ export class CoreIframeUtilsProvider { * Search links () and open them in browser or InAppBrowser if needed. * * @param {any} element Element to treat (iframe, embed, ...). + * @param {boolean} [isSubframe] Whether it's a frame inside another frame. */ - treatFrame(element: any): void { + treatFrame(element: any, isSubframe?: boolean): void { if (element) { + this.checkOnlineFrameInOffline(element, isSubframe); + let winAndDoc = this.getContentWindowAndDocument(element); // Redefine window.open in this element and sub frames, it might have been loaded already. this.redefineWindowOpen(element, winAndDoc.window, winAndDoc.document); @@ -157,6 +228,8 @@ export class CoreIframeUtilsProvider { this.treatFrameLinks(element, winAndDoc.document); element.addEventListener('load', () => { + this.checkOnlineFrameInOffline(element, isSubframe); + // Element loaded, redefine window.open and treat links again. winAndDoc = this.getContentWindowAndDocument(element); this.redefineWindowOpen(element, winAndDoc.window, winAndDoc.document);