commit
29b2184792
|
@ -18,7 +18,6 @@ import { CoreError } from '@classes/errors/error';
|
||||||
import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component';
|
import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component';
|
||||||
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
|
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
import { CoreSites } from '@services/sites';
|
|
||||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { AddonModUrl, AddonModUrlDisplayOptions, AddonModUrlProvider, AddonModUrlUrl } from '../../services/url';
|
import { AddonModUrl, AddonModUrlDisplayOptions, AddonModUrlProvider, AddonModUrlUrl } from '../../services/url';
|
||||||
|
@ -109,8 +108,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
// Fallback in case is not prefetched.
|
// Fallback in case is not prefetched.
|
||||||
const mod =
|
const mod = await CoreCourse.getModule(this.module.id, this.courseId, undefined, false, false, undefined, 'url');
|
||||||
await CoreCourse.getModule(this.module.id, this.courseId, undefined, false, false, undefined, 'url');
|
|
||||||
|
|
||||||
this.name = mod.name;
|
this.name = mod.name;
|
||||||
this.description = mod.description;
|
this.description = mod.description;
|
||||||
|
@ -146,16 +144,6 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
|
||||||
this.isVideo = CoreMimetypeUtils.isExtensionInGroup(extension, ['web_video']);
|
this.isVideo = CoreMimetypeUtils.isExtensionInGroup(extension, ['web_video']);
|
||||||
this.isOther = !this.isImage && !this.isAudio && !this.isVideo;
|
this.isOther = !this.isImage && !this.isAudio && !this.isVideo;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.shouldIframe || (this.shouldEmbed && !this.isImage && !this.isAudio && !this.isVideo)) {
|
|
||||||
// Will be displayed in an iframe. Check if we need to auto-login.
|
|
||||||
const currentSite = CoreSites.getCurrentSite();
|
|
||||||
|
|
||||||
if (currentSite && this.url) {
|
|
||||||
// Format the URL to add auto-login if needed.
|
|
||||||
this.url = await currentSite.getAutoLoginUrl(this.url, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -28,6 +28,8 @@ import { CoreScreen, CoreScreenOrientation } from '@services/screen';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { filter } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
import { NavigationStart } from '@angular/router';
|
import { NavigationStart } from '@angular/router';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreUrl } from '@singletons/url';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'core-iframe',
|
selector: 'core-iframe',
|
||||||
|
@ -45,6 +47,7 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
|
||||||
@Input() allowFullscreen?: boolean | string;
|
@Input() allowFullscreen?: boolean | string;
|
||||||
@Input() showFullscreenOnToolbar?: boolean | string;
|
@Input() showFullscreenOnToolbar?: boolean | string;
|
||||||
@Input() autoFullscreenOnRotate?: boolean | string;
|
@Input() autoFullscreenOnRotate?: boolean | string;
|
||||||
|
@Input() allowAutoLogin = true;
|
||||||
@Output() loaded: EventEmitter<HTMLIFrameElement> = new EventEmitter<HTMLIFrameElement>();
|
@Output() loaded: EventEmitter<HTMLIFrameElement> = new EventEmitter<HTMLIFrameElement>();
|
||||||
|
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
@ -154,9 +157,21 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
|
||||||
*/
|
*/
|
||||||
async ngOnChanges(changes: {[name: string]: SimpleChange }): Promise<void> {
|
async ngOnChanges(changes: {[name: string]: SimpleChange }): Promise<void> {
|
||||||
if (changes.src) {
|
if (changes.src) {
|
||||||
const url = CoreUrlUtils.getYoutubeEmbedUrl(changes.src.currentValue) || changes.src.currentValue;
|
let url = CoreUrlUtils.getYoutubeEmbedUrl(changes.src.currentValue) || changes.src.currentValue;
|
||||||
this.displayHelp = CoreIframeUtils.shouldDisplayHelpForUrl(url);
|
this.displayHelp = CoreIframeUtils.shouldDisplayHelpForUrl(url);
|
||||||
|
|
||||||
|
const currentSite = CoreSites.getCurrentSite();
|
||||||
|
if (this.allowAutoLogin && currentSite) {
|
||||||
|
// Format the URL to add auto-login if needed.
|
||||||
|
url = await currentSite.getAutoLoginUrl(url, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentSite?.isVersionGreaterEqualThan('3.7') && CoreUrl.isVimeoVideoUrl(url)) {
|
||||||
|
// Only treat the Vimeo URL if site is 3.7 or bigger. In older sites the width and height params were mandatory,
|
||||||
|
// and there was no easy way to make the iframe responsive.
|
||||||
|
url = CoreUrl.getVimeoPlayerUrl(url, currentSite) ?? url;
|
||||||
|
}
|
||||||
|
|
||||||
await CoreIframeUtils.fixIframeCookies(url);
|
await CoreIframeUtils.fixIframeCookies(url);
|
||||||
|
|
||||||
this.safeUrl = DomSanitizer.bypassSecurityTrustResourceUrl(CoreFile.convertFileSrc(url));
|
this.safeUrl = DomSanitizer.bypassSecurityTrustResourceUrl(CoreFile.convertFileSrc(url));
|
||||||
|
|
|
@ -46,7 +46,6 @@ import { CoreDirectivesRegistry } from '@singletons/directives-registry';
|
||||||
import { CoreCollapsibleItemDirective } from './collapsible-item';
|
import { CoreCollapsibleItemDirective } from './collapsible-item';
|
||||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
import { AsyncDirective } from '@classes/async-directive';
|
import { AsyncDirective } from '@classes/async-directive';
|
||||||
import { CorePath } from '@singletons/path';
|
|
||||||
import { CoreDom } from '@singletons/dom';
|
import { CoreDom } from '@singletons/dom';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { CoreRefreshContext, CORE_REFRESH_CONTEXT } from '@/core/utils/refresh-context';
|
import { CoreRefreshContext, CORE_REFRESH_CONTEXT } from '@/core/utils/refresh-context';
|
||||||
|
@ -54,6 +53,7 @@ import { CorePlatform } from '@services/platform';
|
||||||
import { ElementController } from '@classes/element-controllers/ElementController';
|
import { ElementController } from '@classes/element-controllers/ElementController';
|
||||||
import { MediaElementController } from '@classes/element-controllers/MediaElementController';
|
import { MediaElementController } from '@classes/element-controllers/MediaElementController';
|
||||||
import { FrameElementController } from '@classes/element-controllers/FrameElementController';
|
import { FrameElementController } from '@classes/element-controllers/FrameElementController';
|
||||||
|
import { CoreUrl } from '@singletons/url';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
|
* Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
|
||||||
|
@ -802,23 +802,8 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
|
||||||
await CoreIframeUtils.fixIframeCookies(src);
|
await CoreIframeUtils.fixIframeCookies(src);
|
||||||
|
|
||||||
if (site && src) {
|
if (site && src) {
|
||||||
// Check if it's a Vimeo video. If it is, use the wsplayer script instead to make restricted videos work.
|
let vimeoUrl = CoreUrl.getVimeoPlayerUrl(src, site);
|
||||||
const matches = src.match(/https?:\/\/player\.vimeo\.com\/video\/([0-9]+)([?&]+h=([a-zA-Z0-9]*))?/);
|
if (vimeoUrl) {
|
||||||
if (matches && matches[1]) {
|
|
||||||
let newUrl = CorePath.concatenatePaths(site.getURL(), '/media/player/vimeo/wsplayer.php?video=') +
|
|
||||||
matches[1] + '&token=' + site.getToken();
|
|
||||||
|
|
||||||
let privacyHash: string | undefined | null = matches[3];
|
|
||||||
if (!privacyHash) {
|
|
||||||
// No privacy hash using the new format. Check the legacy format.
|
|
||||||
const matches = src.match(/https?:\/\/player\.vimeo\.com\/video\/([0-9]+)(\/([a-zA-Z0-9]+))?/);
|
|
||||||
privacyHash = matches && matches[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (privacyHash) {
|
|
||||||
newUrl += `&h=${privacyHash}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const domPromise = CoreDom.waitToBeInDOM(iframe);
|
const domPromise = CoreDom.waitToBeInDOM(iframe);
|
||||||
this.domPromises.push(domPromise);
|
this.domPromises.push(domPromise);
|
||||||
|
|
||||||
|
@ -848,12 +833,12 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
|
||||||
|
|
||||||
// Width and height parameters are required in 3.6 and older sites.
|
// Width and height parameters are required in 3.6 and older sites.
|
||||||
if (site && !site.isVersionGreaterEqualThan('3.7')) {
|
if (site && !site.isVersionGreaterEqualThan('3.7')) {
|
||||||
newUrl += '&width=' + width + '&height=' + height;
|
vimeoUrl += '&width=' + width + '&height=' + height;
|
||||||
}
|
}
|
||||||
|
|
||||||
await CoreIframeUtils.fixIframeCookies(newUrl);
|
await CoreIframeUtils.fixIframeCookies(vimeoUrl);
|
||||||
|
|
||||||
iframe.src = newUrl;
|
iframe.src = vimeoUrl;
|
||||||
|
|
||||||
if (!iframe.width) {
|
if (!iframe.width) {
|
||||||
iframe.width = String(width);
|
iframe.width = String(width);
|
||||||
|
|
|
@ -131,11 +131,8 @@ export class CoreH5PIframeComponent implements OnChanges, OnDestroy {
|
||||||
CoreH5PCore.DISPLAY_OPTION_DOWNLOAD + '=0',
|
CoreH5PCore.DISPLAY_OPTION_DOWNLOAD + '=0',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get auto-login URL so the user is automatically authenticated if needed.
|
|
||||||
const url = await this.site.getAutoLoginUrl(src, false);
|
|
||||||
|
|
||||||
// Add the preventredirect param so the user can authenticate.
|
// Add the preventredirect param so the user can authenticate.
|
||||||
this.iframeSrc = CoreUrlUtils.addParamsToUrl(url, { preventredirect: false });
|
this.iframeSrc = CoreUrlUtils.addParamsToUrl(src, { preventredirect: false });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'Error loading H5P package.', true);
|
CoreDomUtils.showErrorModalDefault(error, 'Error loading H5P package.', true);
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<core-loading [hideUntil]="finalUrl">
|
<core-loading [hideUntil]="url">
|
||||||
<core-iframe *ngIf="finalUrl" [src]="finalUrl"></core-iframe>
|
<core-iframe *ngIf="url" [src]="url" [allowAutoLogin]="autoLogin"></core-iframe>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
|
||||||
import { CoreSites } from '@services/sites';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page to display a URL in an iframe.
|
* Page to display a URL in an iframe.
|
||||||
*/
|
*/
|
||||||
|
@ -29,7 +27,6 @@ export class CoreViewerIframePage implements OnInit {
|
||||||
title?: string; // Page title.
|
title?: string; // Page title.
|
||||||
url?: string; // Iframe URL.
|
url?: string; // Iframe URL.
|
||||||
autoLogin?: boolean; // Whether to try to use auto-login.
|
autoLogin?: boolean; // Whether to try to use auto-login.
|
||||||
finalUrl?: string;
|
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.title = CoreNavigator.getRouteParam('title');
|
this.title = CoreNavigator.getRouteParam('title');
|
||||||
|
@ -38,19 +35,6 @@ export class CoreViewerIframePage implements OnInit {
|
||||||
this.autoLogin = typeof autoLoginParam === 'boolean' ?
|
this.autoLogin = typeof autoLoginParam === 'boolean' ?
|
||||||
autoLoginParam :
|
autoLoginParam :
|
||||||
autoLoginParam !== 'no'; // Support deprecated values yes/no/check.
|
autoLoginParam !== 'no'; // Support deprecated values yes/no/check.
|
||||||
|
|
||||||
if (!this.url) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentSite = CoreSites.getCurrentSite();
|
|
||||||
|
|
||||||
if (currentSite && this.autoLogin) {
|
|
||||||
// Format the URL to add auto-login.
|
|
||||||
this.finalUrl = await currentSite.getAutoLoginUrl(this.url, false);
|
|
||||||
} else {
|
|
||||||
this.finalUrl = this.url;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,6 +231,12 @@ export class CoreIframeUtilsProvider {
|
||||||
* @returns Window and Document.
|
* @returns Window and Document.
|
||||||
*/
|
*/
|
||||||
getContentWindowAndDocument(element: CoreFrameElement): { window: Window | null; document: Document | null } {
|
getContentWindowAndDocument(element: CoreFrameElement): { window: Window | null; document: Document | null } {
|
||||||
|
const src = 'src' in element ? element.src : element.data;
|
||||||
|
if (!CoreUrlUtils.isLocalFileUrl(src)) {
|
||||||
|
// No permissions to access the iframe.
|
||||||
|
return { window: null, document: null };
|
||||||
|
}
|
||||||
|
|
||||||
let contentWindow: Window | null = 'contentWindow' in element ? element.contentWindow : null;
|
let contentWindow: Window | null = 'contentWindow' in element ? element.contentWindow : null;
|
||||||
let contentDocument: Document | null = null;
|
let contentDocument: Document | null = null;
|
||||||
|
|
||||||
|
|
|
@ -270,10 +270,10 @@ export class CoreUrlUtilsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the Youtube Embed Video URL or null if not found.
|
* Returns the Youtube Embed Video URL or undefined if not found.
|
||||||
*
|
*
|
||||||
* @param url URL
|
* @param url URL
|
||||||
* @returns Youtube Embed Video URL or null if not found.
|
* @returns Youtube Embed Video URL or undefined if not found.
|
||||||
*/
|
*/
|
||||||
getYoutubeEmbedUrl(url?: string): string | void {
|
getYoutubeEmbedUrl(url?: string): string | void {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { mock } from '@/testing/utils';
|
||||||
|
import { CoreSite } from '@classes/site';
|
||||||
import { CoreUrl } from '@singletons/url';
|
import { CoreUrl } from '@singletons/url';
|
||||||
|
|
||||||
describe('CoreUrl singleton', () => {
|
describe('CoreUrl singleton', () => {
|
||||||
|
@ -128,4 +130,32 @@ describe('CoreUrl singleton', () => {
|
||||||
expect(CoreUrl.toRelativeURL('https://school.edu', 'school.edu/image.png')).toBe('image.png');
|
expect(CoreUrl.toRelativeURL('https://school.edu', 'school.edu/image.png')).toBe('image.png');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('checks if it is a Vimeo video URL', () => {
|
||||||
|
expect(CoreUrl.isVimeoVideoUrl('')).toEqual(false);
|
||||||
|
expect(CoreUrl.isVimeoVideoUrl('https://player.vimeo.com')).toEqual(false);
|
||||||
|
expect(CoreUrl.isVimeoVideoUrl('https://player.vimeo.com/video/')).toEqual(false);
|
||||||
|
expect(CoreUrl.isVimeoVideoUrl('player.vimeo.com/video/123456')).toEqual(false);
|
||||||
|
expect(CoreUrl.isVimeoVideoUrl('https://player.vimeo.com/video/123456')).toEqual(true);
|
||||||
|
expect(CoreUrl.isVimeoVideoUrl('http://player.vimeo.com/video/123456')).toEqual(true);
|
||||||
|
expect(CoreUrl.isVimeoVideoUrl('https://player.vimeo.com/video/123456/654321?foo=bar')).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets the Vimeo player URL', () => {
|
||||||
|
const siteUrl = 'https://mysite.com';
|
||||||
|
const token = 'mytoken';
|
||||||
|
const site = mock(new CoreSite('42', siteUrl, token));
|
||||||
|
|
||||||
|
// Test basic usage.
|
||||||
|
expect(CoreUrl.getVimeoPlayerUrl('', site)).toEqual(undefined);
|
||||||
|
expect(CoreUrl.getVimeoPlayerUrl('https://somesite.com', site)).toEqual(undefined);
|
||||||
|
expect(CoreUrl.getVimeoPlayerUrl('https://player.vimeo.com/video/123456', site))
|
||||||
|
.toEqual(`${siteUrl}/media/player/vimeo/wsplayer.php?video=123456&token=${token}`);
|
||||||
|
|
||||||
|
// Test privacy hash.
|
||||||
|
expect(CoreUrl.getVimeoPlayerUrl('https://player.vimeo.com/video/123456?h=foo', site))
|
||||||
|
.toEqual(`${siteUrl}/media/player/vimeo/wsplayer.php?video=123456&token=${token}&h=foo`);
|
||||||
|
expect(CoreUrl.getVimeoPlayerUrl('https://player.vimeo.com/video/123456/foo', site))
|
||||||
|
.toEqual(`${siteUrl}/media/player/vimeo/wsplayer.php?video=123456&token=${token}&h=foo`);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CoreSite } from '@classes/site';
|
||||||
import { CorePath } from './path';
|
import { CorePath } from './path';
|
||||||
import { CoreText } from './text';
|
import { CoreText } from './text';
|
||||||
|
|
||||||
|
@ -298,4 +299,49 @@ export class CoreUrl {
|
||||||
return CoreText.removeStartingSlash(CoreUrl.removeProtocol(url).replace(parentUrl, ''));
|
return CoreText.removeStartingSlash(CoreUrl.removeProtocol(url).replace(parentUrl, ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if URL is a Vimeo video URL.
|
||||||
|
*
|
||||||
|
* @param url URL.
|
||||||
|
* @returns Whether is a Vimeo video URL.
|
||||||
|
*/
|
||||||
|
static isVimeoVideoUrl(url: string): boolean {
|
||||||
|
return !!url.match(/https?:\/\/player\.vimeo\.com\/video\/[0-9]+/);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the URL to use to play a Vimeo video if the URL supplied is a Vimeo video URL.
|
||||||
|
* If it's a Vimeo video, the app will use the site's wsplayer script instead to make restricted videos work.
|
||||||
|
*
|
||||||
|
* @param url URL to treat.
|
||||||
|
* @param site Site that contains the URL.
|
||||||
|
* @returns URL, undefined if not a Vimeo video.
|
||||||
|
*/
|
||||||
|
static getVimeoPlayerUrl(
|
||||||
|
url: string,
|
||||||
|
site: CoreSite,
|
||||||
|
): string | undefined {
|
||||||
|
const matches = url.match(/https?:\/\/player\.vimeo\.com\/video\/([0-9]+)([?&]+h=([a-zA-Z0-9]*))?/);
|
||||||
|
if (!matches || !matches[1]) {
|
||||||
|
// Not a Vimeo video.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newUrl = CorePath.concatenatePaths(site.getURL(), '/media/player/vimeo/wsplayer.php?video=') +
|
||||||
|
matches[1] + '&token=' + site.getToken();
|
||||||
|
|
||||||
|
let privacyHash: string | undefined | null = matches[3];
|
||||||
|
if (!privacyHash) {
|
||||||
|
// No privacy hash using the new format. Check the legacy format.
|
||||||
|
const matches = url.match(/https?:\/\/player\.vimeo\.com\/video\/([0-9]+)(\/([a-zA-Z0-9]+))?/);
|
||||||
|
privacyHash = matches && matches[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (privacyHash) {
|
||||||
|
newUrl += `&h=${privacyHash}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUrl;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue