diff --git a/src/addons/mod/url/components/index/index.ts b/src/addons/mod/url/components/index/index.ts index 0739552df..de2899f36 100644 --- a/src/addons/mod/url/components/index/index.ts +++ b/src/addons/mod/url/components/index/index.ts @@ -18,7 +18,6 @@ import { CoreError } from '@classes/errors/error'; import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; import { CoreCourse } from '@features/course/services/course'; -import { CoreSites } from '@services/sites'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreTextUtils } from '@services/utils/text'; import { AddonModUrl, AddonModUrlDisplayOptions, AddonModUrlProvider, AddonModUrlUrl } from '../../services/url'; @@ -109,8 +108,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo } catch { // Fallback in case is not prefetched. - const mod = - await CoreCourse.getModule(this.module.id, this.courseId, undefined, false, false, undefined, 'url'); + const mod = await CoreCourse.getModule(this.module.id, this.courseId, undefined, false, false, undefined, 'url'); this.name = mod.name; this.description = mod.description; @@ -146,16 +144,6 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo this.isVideo = CoreMimetypeUtils.isExtensionInGroup(extension, ['web_video']); 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); - } - } } /** diff --git a/src/core/components/iframe/iframe.ts b/src/core/components/iframe/iframe.ts index 1201da20a..9729eb8a9 100644 --- a/src/core/components/iframe/iframe.ts +++ b/src/core/components/iframe/iframe.ts @@ -28,6 +28,8 @@ import { CoreScreen, CoreScreenOrientation } from '@services/screen'; import { Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import { NavigationStart } from '@angular/router'; +import { CoreSites } from '@services/sites'; +import { CoreUrl } from '@singletons/url'; @Component({ selector: 'core-iframe', @@ -45,6 +47,7 @@ export class CoreIframeComponent implements OnChanges, OnDestroy { @Input() allowFullscreen?: boolean | string; @Input() showFullscreenOnToolbar?: boolean | string; @Input() autoFullscreenOnRotate?: boolean | string; + @Input() allowAutoLogin = true; @Output() loaded: EventEmitter = new EventEmitter(); loading?: boolean; @@ -154,9 +157,21 @@ export class CoreIframeComponent implements OnChanges, OnDestroy { */ async ngOnChanges(changes: {[name: string]: SimpleChange }): Promise { 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); + 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); this.safeUrl = DomSanitizer.bypassSecurityTrustResourceUrl(CoreFile.convertFileSrc(url)); diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts index fa7f57f24..e2c48feab 100644 --- a/src/core/directives/format-text.ts +++ b/src/core/directives/format-text.ts @@ -46,7 +46,6 @@ import { CoreDirectivesRegistry } from '@singletons/directives-registry'; import { CoreCollapsibleItemDirective } from './collapsible-item'; import { CoreCancellablePromise } from '@classes/cancellable-promise'; import { AsyncDirective } from '@classes/async-directive'; -import { CorePath } from '@singletons/path'; import { CoreDom } from '@singletons/dom'; import { CoreEvents } from '@singletons/events'; 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 { MediaElementController } from '@classes/element-controllers/MediaElementController'; 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 @@ -802,23 +802,8 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec await CoreIframeUtils.fixIframeCookies(src); if (site && src) { - // Check if it's a Vimeo video. If it is, use the wsplayer script instead to make restricted videos work. - const matches = src.match(/https?:\/\/player\.vimeo\.com\/video\/([0-9]+)([?&]+h=([a-zA-Z0-9]*))?/); - 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}`; - } - + let vimeoUrl = CoreUrl.getVimeoPlayerUrl(src, site); + if (vimeoUrl) { const domPromise = CoreDom.waitToBeInDOM(iframe); 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. 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) { iframe.width = String(width); diff --git a/src/core/features/h5p/components/h5p-iframe/h5p-iframe.ts b/src/core/features/h5p/components/h5p-iframe/h5p-iframe.ts index a6e0d41d0..ea7bb3e38 100644 --- a/src/core/features/h5p/components/h5p-iframe/h5p-iframe.ts +++ b/src/core/features/h5p/components/h5p-iframe/h5p-iframe.ts @@ -131,11 +131,8 @@ export class CoreH5PIframeComponent implements OnChanges, OnDestroy { 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. - this.iframeSrc = CoreUrlUtils.addParamsToUrl(url, { preventredirect: false }); + this.iframeSrc = CoreUrlUtils.addParamsToUrl(src, { preventredirect: false }); } } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Error loading H5P package.', true); diff --git a/src/core/features/viewer/pages/iframe/iframe.html b/src/core/features/viewer/pages/iframe/iframe.html index 213a23e16..16ecc4b39 100644 --- a/src/core/features/viewer/pages/iframe/iframe.html +++ b/src/core/features/viewer/pages/iframe/iframe.html @@ -9,7 +9,7 @@ - - + + diff --git a/src/core/features/viewer/pages/iframe/iframe.ts b/src/core/features/viewer/pages/iframe/iframe.ts index 7383a50d2..5b6ef0d13 100644 --- a/src/core/features/viewer/pages/iframe/iframe.ts +++ b/src/core/features/viewer/pages/iframe/iframe.ts @@ -15,8 +15,6 @@ import { Component, OnInit } from '@angular/core'; import { CoreNavigator } from '@services/navigator'; -import { CoreSites } from '@services/sites'; - /** * Page to display a URL in an iframe. */ @@ -29,7 +27,6 @@ export class CoreViewerIframePage implements OnInit { title?: string; // Page title. url?: string; // Iframe URL. autoLogin?: boolean; // Whether to try to use auto-login. - finalUrl?: string; async ngOnInit(): Promise { this.title = CoreNavigator.getRouteParam('title'); @@ -38,19 +35,6 @@ export class CoreViewerIframePage implements OnInit { this.autoLogin = typeof autoLoginParam === 'boolean' ? autoLoginParam : 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; - } } } diff --git a/src/core/services/utils/iframe.ts b/src/core/services/utils/iframe.ts index 7cd61dc00..3314fc8dc 100644 --- a/src/core/services/utils/iframe.ts +++ b/src/core/services/utils/iframe.ts @@ -231,6 +231,12 @@ export class CoreIframeUtilsProvider { * @returns Window and Document. */ 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 contentDocument: Document | null = null; diff --git a/src/core/services/utils/url.ts b/src/core/services/utils/url.ts index c7c63dec4..32c2e568d 100644 --- a/src/core/services/utils/url.ts +++ b/src/core/services/utils/url.ts @@ -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 - * @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 { if (!url) { diff --git a/src/core/singletons/tests/url.test.ts b/src/core/singletons/tests/url.test.ts index 40cb31a96..4fd2cf620 100644 --- a/src/core/singletons/tests/url.test.ts +++ b/src/core/singletons/tests/url.test.ts @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { mock } from '@/testing/utils'; +import { CoreSite } from '@classes/site'; import { CoreUrl } from '@singletons/url'; describe('CoreUrl singleton', () => { @@ -128,4 +130,32 @@ describe('CoreUrl singleton', () => { 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`); + }); + }); diff --git a/src/core/singletons/url.ts b/src/core/singletons/url.ts index 2a20a528d..7c2524cd4 100644 --- a/src/core/singletons/url.ts +++ b/src/core/singletons/url.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { CoreSite } from '@classes/site'; import { CorePath } from './path'; import { CoreText } from './text'; @@ -298,4 +299,49 @@ export class CoreUrl { 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; + } + }