diff --git a/moodle.config.json b/moodle.config.json index f96e33bfb..2fc0be9b2 100644 --- a/moodle.config.json +++ b/moodle.config.json @@ -133,5 +133,6 @@ "moodle" ] } - } + }, + "clearIABSessionWhenAutoLogin": "android" } diff --git a/src/addons/mod/url/components/index/addon-mod-url-index.html b/src/addons/mod/url/components/index/addon-mod-url-index.html index 70252ad61..a52ce9833 100644 --- a/src/addons/mod/url/components/index/addon-mod-url-index.html +++ b/src/addons/mod/url/components/index/addon-mod-url-index.html @@ -19,12 +19,12 @@ - + diff --git a/src/addons/mod/url/components/index/index.ts b/src/addons/mod/url/components/index/index.ts index edfb1194c..b6f70314c 100644 --- a/src/addons/mod/url/components/index/index.ts +++ b/src/addons/mod/url/components/index/index.ts @@ -23,6 +23,7 @@ import { CoreTextUtils } from '@services/utils/text'; import { AddonModUrl, AddonModUrlDisplayOptions, AddonModUrlUrl } from '../../services/url'; import { AddonModUrlHelper } from '../../services/url-helper'; import { ADDON_MOD_URL_COMPONENT } from '../../constants'; +import { CoreSites } from '@services/sites'; /** * Component that displays a url. @@ -38,6 +39,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo pluginName = 'url'; url?: string; + embeddedUrl?: string; name?: string; shouldEmbed = false; shouldIframe = false; @@ -137,15 +139,22 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo this.shouldEmbed = displayType == CoreConstants.RESOURCELIB_DISPLAY_EMBED; this.shouldIframe = displayType == CoreConstants.RESOURCELIB_DISPLAY_FRAME; - if (this.shouldEmbed) { - const extension = CoreMimetypeUtils.guessExtensionFromUrl(url.externalurl); - - this.mimetype = CoreMimetypeUtils.getMimeType(extension); - this.isImage = CoreMimetypeUtils.isExtensionInGroup(extension, ['web_image']); - this.isAudio = CoreMimetypeUtils.isExtensionInGroup(extension, ['web_audio']); - this.isVideo = CoreMimetypeUtils.isExtensionInGroup(extension, ['web_video']); - this.isOther = !this.isImage && !this.isAudio && !this.isVideo; + if (!this.shouldEmbed) { + return; } + + const extension = CoreMimetypeUtils.guessExtensionFromUrl(url.externalurl); + + this.mimetype = CoreMimetypeUtils.getMimeType(extension); + this.isImage = CoreMimetypeUtils.isExtensionInGroup(extension, ['web_image']); + this.isAudio = CoreMimetypeUtils.isExtensionInGroup(extension, ['web_audio']); + this.isVideo = CoreMimetypeUtils.isExtensionInGroup(extension, ['web_video']); + this.isOther = !this.isImage && !this.isAudio && !this.isVideo; + + // Fix the URL if it uses pluginfile endpoint. + const currentSite = CoreSites.getCurrentSite(); + this.embeddedUrl = currentSite && this.url ? + await currentSite.checkAndFixPluginfileURL(this.url) : ''; } /** diff --git a/src/core/classes/sites/site.ts b/src/core/classes/sites/site.ts index 9e695d783..2a8af678a 100644 --- a/src/core/classes/sites/site.ts +++ b/src/core/classes/sites/site.ts @@ -54,6 +54,7 @@ import { CoreFilepool } from '@services/filepool'; import { CoreSiteInfo } from './unauthenticated-site'; import { CoreAuthenticatedSite, CoreAuthenticatedSiteOptionalData, CoreSiteWSPreSets, WSObservable } from './authenticated-site'; import { firstValueFrom } from 'rxjs'; +import { CorePlatform } from '@services/platform'; /** * Class that represents a site (combination of site + user). @@ -557,6 +558,17 @@ export class CoreSite extends CoreAuthenticatedSite { // Open the URL. if (inApp) { + if ( + options.clearsessioncache === undefined && autoLoginUrl !== url && + ( + CoreConstants.CONFIG.clearIABSessionWhenAutoLogin === 'all' || + (CoreConstants.CONFIG.clearIABSessionWhenAutoLogin === 'android' && CorePlatform.isAndroid()) || + (CoreConstants.CONFIG.clearIABSessionWhenAutoLogin === 'ios' && CorePlatform.isIOS()) + ) + ) { + options.clearsessioncache = 'yes'; + } + return CoreUtils.openInApp(autoLoginUrl, options); } else { return CoreUtils.openInBrowser(autoLoginUrl, options); diff --git a/src/core/directives/external-content.ts b/src/core/directives/external-content.ts index 156c5bbc2..dbd917e92 100644 --- a/src/core/directives/external-content.ts +++ b/src/core/directives/external-content.ts @@ -23,7 +23,7 @@ import { EventEmitter, OnDestroy, } from '@angular/core'; -import { CoreFile } from '@services/file'; +import { CoreFile, CoreFileProvider } from '@services/file'; import { CoreFilepool, CoreFilepoolFileActions, CoreFilepoolFileEventData } from '@services/filepool'; import { CoreSites } from '@services/sites'; import { CoreUrlUtils } from '@services/utils/url'; @@ -41,6 +41,9 @@ import { CorePromisedValue } from '@classes/promised-value'; import { CorePlatform } from '@services/platform'; import { CoreTextUtils } from '@services/utils/text'; import { CoreArray } from '@singletons/array'; +import { CoreMimetypeUtils } from '@services/utils/mimetype'; +import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; +import { CoreWS } from '@services/ws'; /** * Directive to handle external content. @@ -142,13 +145,12 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O * Get the URL that should be handled and, if valid, handle it. */ protected async checkAndHandleExternalContent(): Promise { - const siteId = this.siteId || CoreSites.getRequiredCurrentSite().getId(); const tagName = this.element.tagName.toUpperCase(); let targetAttr: string; let url: string; // Always handle inline styles (if any). - this.handleInlineStyles(siteId); + this.handleInlineStyles(this.siteId); if (tagName === 'A' || tagName == 'IMAGE') { targetAttr = 'href'; @@ -165,7 +167,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O if (tagName === 'VIDEO' && (this.posterUrl || this.poster)) { // eslint-disable-line deprecation/deprecation // Handle poster. // eslint-disable-next-line deprecation/deprecation - this.handleExternalContent('poster', this.posterUrl ?? this.poster ?? '', siteId).catch(() => { + this.handleExternalContent('poster', this.posterUrl ?? this.poster ?? '').catch(() => { // Ignore errors. }); } @@ -178,7 +180,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O } try { - await this.handleExternalContent(targetAttr, url, siteId); + await this.handleExternalContent(targetAttr, url); } catch (error) { // Error handling content. Make sure the original URL is set. this.setElementUrl(targetAttr, url); @@ -192,28 +194,27 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O * * @param targetAttr Attribute to modify. * @param url Original URL to treat. - * @param siteId Site ID. * @returns Promise resolved if the element is successfully treated. */ - protected async handleExternalContent(targetAttr: string, url: string, siteId?: string): Promise { + protected async handleExternalContent(targetAttr: string, url: string): Promise { const tagName = this.element.tagName; if (tagName == 'VIDEO' && targetAttr != 'poster') { this.handleVideoSubtitles( this.element); } - const site = await CoreSites.getSite(siteId); - const isSiteFile = site.isSitePluginFileUrl(url); + const site = await CoreUtils.ignoreErrors(CoreSites.getSite(this.siteId)); + const isSiteFile = site?.isSitePluginFileUrl(url); if (!url || !url.match(/^https?:\/\//i) || CoreUrlUtils.isLocalFileUrl(url) || - (tagName === 'A' && !(isSiteFile || site.isSiteThemeImageUrl(url) || CoreUrlUtils.isGravatarUrl(url)))) { + (tagName === 'A' && !(isSiteFile || site?.isSiteThemeImageUrl(url) || CoreUrlUtils.isGravatarUrl(url)))) { this.logger.debug('Ignoring non-downloadable URL: ' + url); throw new CoreError('Non-downloadable URL'); } - if (!site.canDownloadFiles() && isSiteFile) { + if (site && !site.canDownloadFiles() && isSiteFile) { this.element.parentElement?.removeChild(this.element); // Remove element since it'll be broken. throw new CoreError(Translate.instant('core.cannotdownloadfiles')); @@ -225,7 +226,9 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O this.setElementUrl(targetAttr, finalUrl); - this.setListeners(targetAttr, url, site); + if (site) { + this.setListeners(targetAttr, url, site); + } } /** @@ -350,7 +353,11 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O * @param site Site. * @returns Promise resolved with the URL. */ - protected async getUrlToUse(targetAttr: string, url: string, site: CoreSite): Promise { + protected async getUrlToUse(targetAttr: string, url: string, site?: CoreSite): Promise { + if (!site) { + return this.getUrlForNoSite(url); + } + const tagName = this.element.tagName; let finalUrl: string; @@ -397,6 +404,36 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O return finalUrl; } + /** + * Get the URL to use when there's no site (the user hasn't authenticated yet). + * In Android, the file will always be downloaded and served from the local file system to avoid problems with cookies. + * In other platforms the file will never be downloaded, we'll always use the online URL. + * + * @param url Original URL to treat. + * @returns Promise resolved with the URL. + */ + protected async getUrlForNoSite(url: string): Promise { + if (!CorePlatform.isAndroid()) { + return url; + } + + const fileId = CoreFilepool.getFileIdByUrl(url); + const extension = CoreMimetypeUtils.guessExtensionFromUrl(url); + + const filePath = CoreFileProvider.NO_SITE_FOLDER + '/' + fileId + (extension ? '.' + extension : ''); + let fileEntry: FileEntry; + + try { + // Check if the file is already downloaded. + fileEntry = await CoreFile.getFile(filePath); + } catch { + // File not downloaded, download it first. + fileEntry = await CoreWS.downloadFile(url, filePath, false); + } + + return CoreFile.convertFileSrc(CoreFile.getFileEntryURL(fileEntry)); + } + /** * Set listeners if needed. * diff --git a/src/core/features/course/components/module-summary/module-summary.html b/src/core/features/course/components/module-summary/module-summary.html index 64cb80e6d..202d5ad12 100644 --- a/src/core/features/course/components/module-summary/module-summary.html +++ b/src/core/features/course/components/module-summary/module-summary.html @@ -81,7 +81,8 @@ - + diff --git a/src/core/features/grades/pages/course/course.html b/src/core/features/grades/pages/course/course.html index 1627cddd7..93cb0270d 100644 --- a/src/core/features/grades/pages/course/course.html +++ b/src/core/features/grades/pages/course/course.html @@ -43,7 +43,7 @@ class="expandable-status-icon" [class.expandable-status-icon-expanded]="row.expanded" /> + [alt]="row.iconAlt" core-external-content /> diff --git a/src/core/features/login/pages/credentials/credentials.html b/src/core/features/login/pages/credentials/credentials.html index 30eee5eea..3e7cf76f3 100644 --- a/src/core/features/login/pages/credentials/credentials.html +++ b/src/core/features/login/pages/credentials/credentials.html @@ -25,7 +25,8 @@