From 6f1bdec29df563e4a76bf17b469905e5035a82ec Mon Sep 17 00:00:00 2001
From: Dani Palou <dani@moodle.com>
Date: Mon, 20 Jan 2025 15:00:00 +0100
Subject: [PATCH 1/3] MOBILE-4741 core: Fix lang param in openInBrowser with
 auto-login

When using auto-login, the lang parameter should be added to the urltogo instead of the autologin endpoint
---
 .../classes/sites/unauthenticated-site.ts     |  2 +-
 src/core/services/utils/url.ts                |  2 +-
 src/core/singletons/opener.ts                 | 14 ++---
 src/core/singletons/tests/url.test.ts         | 63 +++++++++----------
 src/core/singletons/url.ts                    | 41 ++++++++----
 upgrade.txt                                   |  1 +
 6 files changed, 68 insertions(+), 55 deletions(-)

diff --git a/src/core/classes/sites/unauthenticated-site.ts b/src/core/classes/sites/unauthenticated-site.ts
index e213cc4f2f3..05ab1c80bf0 100644
--- a/src/core/classes/sites/unauthenticated-site.ts
+++ b/src/core/classes/sites/unauthenticated-site.ts
@@ -219,7 +219,7 @@ export class CoreUnauthenticatedSite {
      * @returns URL with params.
      */
     createSiteUrl(path: string, params?: Record<string, unknown>, anchor?: string): string {
-        return CoreUrl.addParamsToUrl(CorePath.concatenatePaths(this.siteUrl, path), params, anchor);
+        return CoreUrl.addParamsToUrl(CorePath.concatenatePaths(this.siteUrl, path), params, { anchor });
     }
 
     /**
diff --git a/src/core/services/utils/url.ts b/src/core/services/utils/url.ts
index d00d4e62e5c..80c413a17dc 100644
--- a/src/core/services/utils/url.ts
+++ b/src/core/services/utils/url.ts
@@ -46,7 +46,7 @@ export class CoreUrlUtilsProvider {
      * @deprecated since 4.5. Use CoreUrl.addParamsToUrl instead.
      */
     addParamsToUrl(url: string, params?: Record<string, unknown>, anchor?: string, boolToNumber?: boolean): string {
-        return CoreUrl.addParamsToUrl(url, params, anchor, boolToNumber);
+        return CoreUrl.addParamsToUrl(url, params, { anchor, boolToNumber });
     }
 
     /**
diff --git a/src/core/singletons/opener.ts b/src/core/singletons/opener.ts
index c8fbc59acae..f6bdec3c1cb 100644
--- a/src/core/singletons/opener.ts
+++ b/src/core/singletons/opener.ts
@@ -190,14 +190,14 @@ export class CoreOpener {
             }
         }
 
-        const site = CoreSites.getCurrentSite();
+        if (CoreSites.getCurrentSite()?.containsUrl(url)) {
+            url = CoreUrl.addParamsToUrl(url, { lang: await CoreLang.getCurrentLanguage(CoreLangFormat.LMS) }, {
+                checkAutoLoginUrl: options.originalUrl !== url,
+            });
+        }
+
         CoreAnalytics.logEvent({ type: CoreAnalyticsEventType.OPEN_LINK, link: originaUrl });
-        window.open(
-            site?.containsUrl(url)
-                ? CoreUrl.addParamsToUrl(url, { lang: await CoreLang.getCurrentLanguage(CoreLangFormat.LMS) })
-                : url,
-            '_system',
-        );
+        window.open(url, '_system');
     }
 
     /**
diff --git a/src/core/singletons/tests/url.test.ts b/src/core/singletons/tests/url.test.ts
index 697b383d821..3684cb47c82 100644
--- a/src/core/singletons/tests/url.test.ts
+++ b/src/core/singletons/tests/url.test.ts
@@ -83,55 +83,50 @@ describe('CoreUrl singleton', () => {
         expect(url).toEqual('https://moodle.org');
     });
 
-    it('adds params to URL without params', () => {
-        const originalUrl = 'https://moodle.org';
-        const params = {
+    it('adds params and anchors to URLs', () => {
+        // Add params to a URL without params.
+        expect(CoreUrl.addParamsToUrl('https://moodle.org', {
             first: '1',
             second: '2',
-        };
-        const url = CoreUrl.addParamsToUrl(originalUrl, params);
-
-        expect(url).toEqual('https://moodle.org?first=1&second=2');
-    });
+        })).toEqual('https://moodle.org?first=1&second=2');
 
-    it('adds params to URL with existing params', () => {
-        const originalUrl = 'https://moodle.org?existing=1';
-        const params = {
+        // Add params to a URL with existing params.
+        expect(CoreUrl.addParamsToUrl('https://moodle.org?existing=1', {
             first: '1',
             second: '2',
-        };
-        const url = CoreUrl.addParamsToUrl(originalUrl, params);
-
-        expect(url).toEqual('https://moodle.org?existing=1&first=1&second=2');
-    });
-
-    it('doesn\'t change URL if no params supplied', () => {
-        const originalUrl = 'https://moodle.org';
-        const url = CoreUrl.addParamsToUrl(originalUrl);
+        })).toEqual('https://moodle.org?existing=1&first=1&second=2');
 
-        expect(url).toEqual(originalUrl);
-    });
+        // No params supplied.
+        expect(CoreUrl.addParamsToUrl('https://moodle.org')).toEqual('https://moodle.org');
 
-    it('doesn\'t add undefined or null params', () => {
-        const originalUrl = 'https://moodle.org';
-        const url = CoreUrl.addParamsToUrl(originalUrl, {
+        // Undefined or null params aren't added.
+        expect(CoreUrl.addParamsToUrl('https://moodle.org', {
             foo: undefined,
             bar: null,
             baz: 1,
-        });
+        })).toEqual('https://moodle.org?baz=1');
 
-        expect(url).toEqual('https://moodle.org?baz=1');
-    });
+        // Adds anchor to URL.
+        expect(CoreUrl.addParamsToUrl('https://moodle.org', {
+            first: '1',
+            second: '2',
+        }, {
+            anchor: 'myanchor',
+        })).toEqual('https://moodle.org?first=1&second=2#myanchor');
 
-    it('adds anchor to URL', () => {
-        const originalUrl = 'https://moodle.org';
-        const params = {
+        // Adds params to the urltogo in case it's an auto-login URL.
+        expect(CoreUrl.addParamsToUrl('https://mysite.com/autologin.php?urltogo=https%3A%2F%2Fmoodle.org', {
             first: '1',
             second: '2',
-        };
-        const url = CoreUrl.addParamsToUrl(originalUrl, params, 'myanchor');
+        }, {
+            checkAutoLoginUrl: true,
+        })).toEqual('https://mysite.com/autologin.php?urltogo=https%3A%2F%2Fmoodle.org%3Ffirst%3D1%26second%3D2');
 
-        expect(url).toEqual('https://moodle.org?first=1&second=2#myanchor');
+        // Adds params to the base URL even if it has urltogo if checkAutoLoginUrl is not set.
+        expect(CoreUrl.addParamsToUrl('https://mysite.com/autologin.php?urltogo=https%3A%2F%2Fmoodle.org', {
+            first: '1',
+            second: '2',
+        })).toEqual('https://mysite.com/autologin.php?urltogo=https%3A%2F%2Fmoodle.org&first=1&second=2');
     });
 
     it('parses standard urls', () => {
diff --git a/src/core/singletons/url.ts b/src/core/singletons/url.ts
index 29e7db27d13..d1415474b07 100644
--- a/src/core/singletons/url.ts
+++ b/src/core/singletons/url.ts
@@ -403,28 +403,31 @@ export class CoreUrl {
      *
      * @param url URL to add the params to.
      * @param params Object with the params to add.
-     * @param anchor Anchor text if needed.
-     * @param boolToNumber Whether to convert bools to 1 or 0.
+     * @param options Other options.
      * @returns URL with params.
      */
-    static addParamsToUrl(url: string, params?: Record<string, unknown>, anchor?: string, boolToNumber?: boolean): string {
+    static addParamsToUrl(url: string, params?: Record<string, unknown>, options: CoreUrlAddParamsOptions = {}): string {
+        // If it's an auto-login URL, add the params to the urltogo. extractUrlParams returns the urltogo already decoded.
+        const urlParams = options.checkAutoLoginUrl ? CoreUrl.extractUrlParams(url) : undefined;
+        let urlToTreat = urlParams?.urltogo ?? url;
+
         // Remove any existing anchor to add the params before it.
-        const urlAndAnchor = url.split('#');
-        url = urlAndAnchor[0];
+        const urlAndAnchor = urlToTreat.split('#');
+        urlToTreat = urlAndAnchor[0];
 
-        let separator = url.indexOf('?') !== -1 ? '&' : '?';
+        let separator = urlToTreat.indexOf('?') !== -1 ? '&' : '?';
 
         for (const key in params) {
             let value = params[key];
 
-            if (boolToNumber && typeof value === 'boolean') {
+            if (options.boolToNumber && typeof value === 'boolean') {
                 // Convert booleans to 1 or 0.
                 value = value ? '1' : '0';
             }
 
             // Ignore objects and undefined.
             if (typeof value !== 'object' && value !== undefined) {
-                url += separator + key + '=' + value;
+                urlToTreat += separator + key + '=' + value;
                 separator = '&';
             }
         }
@@ -435,14 +438,19 @@ export class CoreUrl {
             urlAndAnchor.shift();
 
             // Use a join in case there is more than one #.
-            url += '#' + urlAndAnchor.join('#');
+            urlToTreat += '#' + urlAndAnchor.join('#');
         }
 
-        if (anchor) {
-            url += '#' + anchor;
+        if (options.anchor) {
+            urlToTreat += '#' + options.anchor;
         }
 
-        return url;
+        if (!urlParams?.urltogo) {
+            return urlToTreat;
+        }
+
+        // Replace the urltogo with the treated one.
+        return url.replace(encodeURIComponent(urlParams.urltogo), encodeURIComponent(urlToTreat));
     }
 
     /**
@@ -997,3 +1005,12 @@ export class CoreUrl {
 }
 
 export type CoreUrlParams = {[key: string]: string};
+
+/**
+ * Options for addParamsToUrl.
+ */
+export type CoreUrlAddParamsOptions = {
+    anchor?: string; // Anchor text if needed.
+    boolToNumber?: boolean; // Whether to convert bools to 1 / 0.
+    checkAutoLoginUrl?: boolean; // Whether the URL could be an auto-login URL. If so, any param will be added to the urltogo.
+};
diff --git a/upgrade.txt b/upgrade.txt
index 9d24bc4290e..afbffdbf044 100644
--- a/upgrade.txt
+++ b/upgrade.txt
@@ -7,6 +7,7 @@ For more information about upgrading, read the official documentation: https://m
  - The logout process has been refactored, now it uses a logout page to trigger Angular guards. CoreSites.logout now uses this process, and CoreSites.logoutForRedirect is deprecated and shouldn't be used anymore.
  - The parameters of treatDownloadedFile of plugin file handlers have changed. Now the third parameter is an object with all the optional parameters.
  - Some CoreColors functions have been refactored to handle alpha and to validate colors.
+ - The parameters of CoreUrl.addParamsToUrl have changed. Now the third parameter is an object with all the optional parameters.
 
 === 4.5.0 ===
 

From 6aff33794612d8bf8baf30048fd7f9396ed66d01 Mon Sep 17 00:00:00 2001
From: Dani Palou <dani@moodle.com>
Date: Mon, 20 Jan 2025 15:16:52 +0100
Subject: [PATCH 2/3] MOBILE-4741 core: Pass lang parameter when opening IAB
 too

---
 src/core/features/user/services/support.ts | 10 ++++++----
 src/core/singletons/opener.ts              |  6 ++++++
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/src/core/features/user/services/support.ts b/src/core/features/user/services/support.ts
index bcf2d0ec787..1698b33f4ea 100644
--- a/src/core/features/user/services/support.ts
+++ b/src/core/features/user/services/support.ts
@@ -24,7 +24,6 @@ import { CoreSubscriptions } from '@singletons/subscriptions';
 import { AlertButton } from '@ionic/angular';
 import { CoreLang } from '@services/lang';
 import { CoreUserNullSupportConfig } from '@features/user/classes/support/null-support-config';
-import { CoreOpener } from '@singletons/opener';
 import { CoreAlerts } from '@services/overlays/alerts';
 
 /**
@@ -41,10 +40,9 @@ export class CoreUserSupportService {
     async contact(options: CoreUserSupportContactOptions = {}): Promise<void> {
         const supportConfig = options.supportConfig ?? CoreUserAuthenticatedSupportConfig.forCurrentSite();
         const supportPageUrl = supportConfig.getSupportPageUrl();
-        const autoLoginUrl = await CoreSites.getCurrentSite()?.getAutoLoginUrl(supportPageUrl, false);
-        const browser = CoreOpener.openInApp(autoLoginUrl ?? supportPageUrl);
+        const browser = await CoreSites.getCurrentSite()?.openInAppWithAutoLogin(supportPageUrl);
 
-        if (supportPageUrl.endsWith('/user/contactsitesupport.php')) {
+        if (browser && supportPageUrl.endsWith('/user/contactsitesupport.php')) {
             this.populateSupportForm(browser, options.subject, options.message);
             this.listenSupportFormSubmission(browser, supportConfig.getSupportPageLang());
         }
@@ -121,6 +119,10 @@ export class CoreUserSupportService {
      * @param lang Language used in the support page.
      */
     protected async listenSupportFormSubmission(browser: InAppBrowserObject, lang: string | null): Promise<void> {
+        if (!CorePlatform.isMobile()) {
+            return;
+        }
+
         const appSuccessMessage = Translate.instant('core.user.supportmessagesent');
         const lmsSuccessMessage = lang && await CoreLang.getMessage('core.user.supportmessagesent', lang);
         const subscription = browser.on('loadstop').subscribe(async () => {
diff --git a/src/core/singletons/opener.ts b/src/core/singletons/opener.ts
index f6bdec3c1cb..62aafd90321 100644
--- a/src/core/singletons/opener.ts
+++ b/src/core/singletons/opener.ts
@@ -309,6 +309,12 @@ export class CoreOpener {
 
         CoreOpener.setInAppBrowserToolbarColors(options);
 
+        if (CoreSites.getCurrentSite()?.containsUrl(url)) {
+            url = CoreUrl.addParamsToUrl(url, { lang: CoreLang.getCurrentLanguageSync(CoreLangFormat.LMS) }, {
+                checkAutoLoginUrl: options.originalUrl !== url,
+            });
+        }
+
         CoreOpener.iabInstance = InAppBrowser.create(url, '_blank', options);
 
         if (CorePlatform.isMobile()) {

From c8c0edf8bdb1b2d297566dc2f84e6158ad7ebdbe Mon Sep 17 00:00:00 2001
From: Dani Palou <dani@moodle.com>
Date: Mon, 20 Jan 2025 15:47:52 +0100
Subject: [PATCH 3/3] MOBILE-4741 core: Pass lang parameter in iframes too

---
 src/core/components/iframe/iframe.ts | 14 +++++++++++---
 src/core/directives/format-text.ts   |  9 ++++++++-
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/core/components/iframe/iframe.ts b/src/core/components/iframe/iframe.ts
index fdd472b85f8..5836438b984 100644
--- a/src/core/components/iframe/iframe.ts
+++ b/src/core/components/iframe/iframe.ts
@@ -30,6 +30,7 @@ import { CoreSites } from '@services/sites';
 import { toBoolean } from '@/core/transforms/boolean';
 import { CoreDom } from '@singletons/dom';
 import { CoreAlerts } from '@services/overlays/alerts';
+import { CoreLang, CoreLangFormat } from '@services/lang';
 
 @Component({
     selector: 'core-iframe',
@@ -234,9 +235,16 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
             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?.containsUrl(url)) {
+                // Format the URL to add auto-login if needed and add the lang parameter.
+                const autoLoginUrl = this.allowAutoLogin ?
+                    await currentSite.getAutoLoginUrl(url, false) :
+                    url;
+
+                const lang = await CoreLang.getCurrentLanguage(CoreLangFormat.LMS);
+                url = CoreUrl.addParamsToUrl(autoLoginUrl, { lang }, {
+                    checkAutoLoginUrl: autoLoginUrl !== url,
+                });
             }
 
             if (currentSite?.isVersionGreaterEqualThan('3.7') && CoreUrl.isVimeoVideoUrl(url)) {
diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts
index 9ad3c2ed52b..63857fe912d 100644
--- a/src/core/directives/format-text.ts
+++ b/src/core/directives/format-text.ts
@@ -60,6 +60,7 @@ import { toBoolean } from '../transforms/boolean';
 import { CoreViewer } from '@features/viewer/services/viewer';
 import { CorePromiseUtils } from '@singletons/promise-utils';
 import { CoreAlerts } from '@services/overlays/alerts';
+import { CoreLang, CoreLangFormat } from '@services/lang';
 
 /**
  * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
@@ -833,7 +834,13 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
             // Remove iframe src, otherwise it can cause auto-login issues if there are several iframes with auto-login.
             iframe.src = '';
 
-            const finalUrl = await CoreIframeUtils.getAutoLoginUrlForIframe(iframe, src);
+            let finalUrl = await CoreIframeUtils.getAutoLoginUrlForIframe(iframe, src);
+
+            const lang = await CoreLang.getCurrentLanguage(CoreLangFormat.LMS);
+            finalUrl = CoreUrl.addParamsToUrl(finalUrl, { lang }, {
+                checkAutoLoginUrl: src !== finalUrl,
+            });
+
             await CoreIframeUtils.fixIframeCookies(finalUrl);
 
             iframe.src = finalUrl;