Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 36 additions & 86 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"cordova-plugin-splashscreen": "5.0.3",
"cordova-plugin-statusbar": "2.4.3",
"cordova-plugin-whitelist": "1.3.4",
"cordova-plugin-wkuserscript": "git+https://github.com/moodlemobile/cordova-plugin-wkuserscript.git",
"cordova-plugin-wkwebview-cookies": "git+https://github.com/moodlemobile/cordova-plugin-wkwebview-cookies.git",
"cordova-plugin-zip": "3.1.0",
"cordova-sqlite-storage": "4.0.0",
Expand Down Expand Up @@ -211,7 +212,8 @@
},
"cordova-plugin-wkwebview-cookies": {},
"cordova-plugin-qrscanner": {},
"cordova-plugin-chooser": {}
"cordova-plugin-chooser": {},
"cordova-plugin-wkuserscript": {}
}
},
"main": "desktop/electron.js",
Expand Down
2 changes: 1 addition & 1 deletion src/addon/mod/h5pactivity/components/index/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
* @return Whether it's an XAPI post statement of the current activity.
*/
protected isCurrentXAPIPost(data: any): boolean {
if (data.context != 'moodleapp' || data.action != 'xapi_post_statement' || !data.statements) {
if (data.environment != 'moodleapp' || data.context != 'h5p' || data.action != 'xapi_post_statement' || !data.statements) {
return false;
}

Expand Down
42 changes: 42 additions & 0 deletions src/assets/js/iframe-recaptcha.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

(function () {
var url = location.href;

if (!url.match(/^https?:\/\//i) || !url.match(/\/webservice\/recaptcha\.php/i)) {
// Not the recaptcha script, stop.
return;
}

// Define recaptcha callbacks.
window.recaptchacallback = function(value) {
window.parent.postMessage({
environment: 'moodleapp',
context: 'recaptcha',
action: 'callback',
frameUrl: location.href,
value: value,
}, '*');
};

window.recaptchaexpiredcallback = function() {
window.parent.postMessage({
environment: 'moodleapp',
context: 'recaptcha',
action: 'expired',
frameUrl: location.href,
}, '*');
};
})();
210 changes: 210 additions & 0 deletions src/assets/js/iframe-treat-links.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

(function () {
var url = location.href;

if (url.match(/^moodleappfs:\/\/localhost/i) || !url.match(/^[a-z0-9]+:\/\//i)) {
// Same domain as the app, stop.
return;
}

// Redefine window.open.
window.open = function(url, name, specs) {
if (name == '_self') {
// Link should be loaded in the same frame.
location.href = toAbsolute(url);

return;
}

getRootWindow(window).postMessage({
environment: 'moodleapp',
context: 'iframe',
action: 'window_open',
frameUrl: location.href,
url: url,
name: name,
specs: specs,
}, '*');
};

// Handle link clicks.
document.addEventListener('click', (event) => {
if (event.defaultPrevented) {
// Event already prevented by some other code.
return;
}

// Find the link being clicked.
var el = event.target;
while (el && el.tagName !== 'A') {
el = el.parentElement;
}

if (!el || el.treated) {
return;
}

// Add click listener to the link, this way if the iframe has added a listener to the link it will be executed first.
el.treated = true;
el.addEventListener('click', function(event) {
linkClicked(el, event);
});
}, {
capture: true // Use capture to fix this listener not called if the element clicked is too deep in the DOM.
});



/**
* Concatenate two paths, adding a slash between them if needed.
*
* @param leftPath Left path.
* @param rightPath Right path.
* @return Concatenated path.
*/
function concatenatePaths(leftPath, rightPath) {
if (!leftPath) {
return rightPath;
} else if (!rightPath) {
return leftPath;
}

var lastCharLeft = leftPath.slice(-1);
var firstCharRight = rightPath.charAt(0);

if (lastCharLeft === '/' && firstCharRight === '/') {
return leftPath + rightPath.substr(1);
} else if (lastCharLeft !== '/' && firstCharRight !== '/') {
return leftPath + '/' + rightPath;
} else {
return leftPath + rightPath;
}
}

/**
* Get the root window.
*
* @param win Current window to check.
* @return Root window.
*/
function getRootWindow(win) {
if (win.parent === win) {
return win;
}

return getRootWindow(win.parent);
}

/**
* Get the scheme from a URL.
*
* @param url URL to treat.
* @return Scheme, undefined if no scheme found.
*/
function getUrlScheme(url) {
if (!url) {
return;
}

var matches = url.match(/^([a-z][a-z0-9+\-.]*):/);
if (matches && matches[1]) {
return matches[1];
}
}

/**
* Check if a URL is absolute.
*
* @param url URL to treat.
* @return Whether it's absolute.
*/
function isAbsoluteUrl(url) {
return /^[^:]{2,}:\/\//i.test(url);
}

/**
* Check whether a URL scheme belongs to a local file.
*
* @param scheme Scheme to check.
* @return Whether the scheme belongs to a local file.
*/
function isLocalFileUrlScheme(scheme) {
if (scheme) {
scheme = scheme.toLowerCase();
}

return scheme == 'cdvfile' ||
scheme == 'file' ||
scheme == 'filesystem' ||
scheme == 'moodleappfs';
}

/**
* Handle a click on an anchor element.
*
* @param link Anchor element clicked.
* @param event Click event.
*/
function linkClicked(link, event) {
if (event.defaultPrevented) {
// Event already prevented by some other code.
return;
}

var linkScheme = getUrlScheme(link.href);
var pageScheme = getUrlScheme(location.href);
var isTargetSelf = !link.target || link.target == '_self';

if (!link.href || linkScheme == 'javascript') {
// Links with no URL and Javascript links are ignored.
return;
}

event.preventDefault();

if (isTargetSelf && (isLocalFileUrlScheme(linkScheme) || !isLocalFileUrlScheme(pageScheme))) {
// Link should be loaded in the same frame. Don't do it if link is online and frame is local.
location.href = toAbsolute(link.href);

return;
}

getRootWindow(window).postMessage({
environment: 'moodleapp',
context: 'iframe',
action: 'link_clicked',
frameUrl: location.href,
link: {href: link.href, target: link.target},
}, '*');
}

/**
* Convert a URL to an absolute URL if needed using the frame src.
*
* @param url URL to convert.
* @return Absolute URL.
*/
function toAbsolute(url) {
if (isAbsoluteUrl(url)) {
return url;
}

// It's a relative URL, use the frame src to create the full URL.
var pathToDir = location.href.substring(0, location.href.lastIndexOf('/'));

return concatenatePaths(pathToDir, url);
}
})();
6 changes: 4 additions & 2 deletions src/components/iframe/core-iframe.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<div [class.core-loading-container]="loading" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}">
<iframe #iframe [hidden]="loading" class="core-iframe" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl" [attr.allowfullscreen]="allowFullscreen ? 'allowfullscreen' : null"></iframe>
<div [class.core-loading-container]="loading || !safeUrl" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}">
<!-- Don't add the iframe until the safeUrl is set, adding an iframe with null as src causes the iframe to load the whole app. -->
<iframe #iframe *ngIf="safeUrl" [hidden]="loading" class="core-iframe" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl" [attr.allowfullscreen]="allowFullscreen ? 'allowfullscreen' : null"></iframe>

<span class="core-loading-spinner">
<ion-spinner *ngIf="loading"></ion-spinner>
</span>
Expand Down
23 changes: 18 additions & 5 deletions src/components/iframe/iframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

import {
Component, Input, Output, OnInit, ViewChild, ElementRef, EventEmitter, OnChanges, SimpleChange, Optional
Component, Input, Output, ViewChild, ElementRef, EventEmitter, OnChanges, SimpleChange, Optional
} from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { NavController, Platform } from 'ionic-angular';
Expand All @@ -25,12 +25,13 @@ import { CoreIframeUtilsProvider } from '@providers/utils/iframe';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreUrl } from '@singletons/url';
import { WKWebViewCookiesWindow } from 'cordova-plugin-wkwebview-cookies';

@Component({
selector: 'core-iframe',
templateUrl: 'core-iframe.html'
})
export class CoreIframeComponent implements OnInit, OnChanges {
export class CoreIframeComponent implements OnChanges {

@ViewChild('iframe') iframe: ElementRef;
@Input() src: string;
Expand All @@ -43,6 +44,7 @@ export class CoreIframeComponent implements OnInit, OnChanges {

protected logger;
protected IFRAME_TIMEOUT = 15000;
protected initialized = false;

constructor(logger: CoreLoggerProvider,
protected iframeUtils: CoreIframeUtilsProvider,
Expand All @@ -59,9 +61,15 @@ export class CoreIframeComponent implements OnInit, OnChanges {
}

/**
* Component being initialized.
* Init the data.
*/
ngOnInit(): void {
protected init(): void {
if (this.initialized) {
return;
}

this.initialized = true;

const iframe: HTMLIFrameElement = this.iframe && this.iframe.nativeElement;

this.iframeWidth = this.domUtils.formatPixelsSize(this.iframeWidth) || '100%';
Expand Down Expand Up @@ -101,7 +109,7 @@ export class CoreIframeComponent implements OnInit, OnChanges {
if (this.platform.is('ios') && !this.urlUtils.isLocalFileUrl(url)) {
// Save a "fake" cookie for the iframe's domain to fix a bug in WKWebView.
try {
const win = <any> window;
const win = <WKWebViewCookiesWindow> window;
const urlParts = CoreUrl.parse(url);

await win.WKWebViewCookies.setCookie({
Expand All @@ -116,6 +124,11 @@ export class CoreIframeComponent implements OnInit, OnChanges {
}

this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(CoreFile.instance.convertFileSrc(url));

// Now that the URL has been set, initialize the iframe. Wait for the iframe to the added to the DOM.
setTimeout(() => {
this.init();
});
}
}
}
Loading