diff --git a/src/core/features/contentlinks/services/contentlinks-delegate.ts b/src/core/features/contentlinks/services/contentlinks-delegate.ts
index 2b3e1a93c..b83b4109e 100644
--- a/src/core/features/contentlinks/services/contentlinks-delegate.ts
+++ b/src/core/features/contentlinks/services/contentlinks-delegate.ts
@@ -216,16 +216,7 @@ export class CoreContentLinksDelegateService {
}
// Site is logged out, authenticate first before treating the URL.
- const willReload = await CoreSites.logoutForRedirect(siteId, {
- urlToOpen: url,
- });
-
- if (!willReload) {
- // Load the site with the redirect data.
- await CoreSites.loadSite(siteId, {
- urlToOpen: url,
- });
- }
+ await CoreSites.logout({ urlToOpen: url, siteId });
};
});
diff --git a/src/core/features/login/login.module.ts b/src/core/features/login/login.module.ts
index b0d3d8fbd..8030f6411 100644
--- a/src/core/features/login/login.module.ts
+++ b/src/core/features/login/login.module.ts
@@ -41,6 +41,10 @@ const appRoutes: Routes = [
loadChildren: () => import('./login-lazy.module'),
canActivate: [redirectGuard],
},
+ {
+ path: 'logout',
+ loadComponent: () => import('@features/login/pages/logout/logout'),
+ },
];
@NgModule({
diff --git a/src/core/features/login/pages/logout/logout.html b/src/core/features/login/pages/logout/logout.html
new file mode 100644
index 000000000..1e61e89a5
--- /dev/null
+++ b/src/core/features/login/pages/logout/logout.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/core/features/login/pages/logout/logout.ts b/src/core/features/login/pages/logout/logout.ts
new file mode 100644
index 000000000..db0bc10ff
--- /dev/null
+++ b/src/core/features/login/pages/logout/logout.ts
@@ -0,0 +1,104 @@
+// (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.
+
+import { Component, OnInit } from '@angular/core';
+import { CoreSites } from '@services/sites';
+import { CoreConstants } from '@/core/constants';
+import { CoreNavigationOptions, CoreNavigator, CoreRedirectPayload } from '@services/navigator';
+import { CoreSharedModule } from '@/core/shared.module';
+import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
+import { CoreRedirects } from '@singletons/redirects';
+
+/**
+ * Page that logs the user out.
+ */
+@Component({
+ selector: 'page-core-login-logout',
+ templateUrl: 'logout.html',
+ standalone: true,
+ imports: [
+ CoreSharedModule,
+ ],
+})
+export default class CoreLoginLogoutPage implements OnInit {
+
+ /**
+ * @inheritdoc
+ */
+ async ngOnInit(): Promise {
+ const siteId = CoreNavigator.getRouteParam('siteId') ?? CoreConstants.NO_SITE_ID;
+ const logoutOptions = {
+ forceLogout: CoreNavigator.getRouteBooleanParam('forceLogout'),
+ removeAccount: CoreNavigator.getRouteBooleanParam('removeAccount') ?? !!CoreConstants.CONFIG.removeaccountonlogout,
+ };
+ const redirectData = {
+ redirectPath: CoreNavigator.getRouteParam('redirectPath'),
+ redirectOptions: CoreNavigator.getRouteParam('redirectOptions'),
+ urlToOpen: CoreNavigator.getRouteParam('urlToOpen'),
+ };
+
+ if (!CoreSites.isLoggedIn()) {
+ // This page shouldn't open if user isn't logged in, but if that happens just navigate to the right page.
+ await this.navigateAfterLogout(siteId, redirectData);
+
+ return;
+ }
+
+ const shouldReload = CoreSitePlugins.hasSitePluginsLoaded;
+ if (shouldReload && (siteId !== CoreConstants.NO_SITE_ID || redirectData.redirectPath || redirectData.urlToOpen)) {
+ // The app will reload and we need to open a page that isn't the default page. Store the redirect first.
+ CoreRedirects.storeRedirect(siteId, redirectData);
+ }
+
+ await CoreSites.internalLogout(logoutOptions);
+
+ if (shouldReload) {
+ // We need to reload the app to unload all the plugins. Leave the logout page first.
+ await CoreNavigator.navigate('/login', { reset: true });
+
+ window.location.reload();
+
+ return;
+ }
+
+ await this.navigateAfterLogout(siteId, redirectData);
+ }
+
+ /**
+ * Navigate to the right page after logout is done.
+ *
+ * @param siteId Site ID to load.
+ * @param redirectData Redirect data.
+ */
+ protected async navigateAfterLogout(siteId: string, redirectData: CoreRedirectPayload): Promise {
+ if (siteId === CoreConstants.NO_SITE_ID) {
+ // No site to load now, just navigate.
+ await CoreNavigator.navigate(redirectData.redirectPath ?? '/login/sites', {
+ ...redirectData.redirectOptions,
+ reset: true,
+ });
+
+ return;
+ }
+
+ // Load the site and navigate.
+ const loggedIn = await CoreSites.loadSite(siteId, redirectData);
+ if (!loggedIn) {
+ return; // Session expired.
+ }
+
+ await CoreNavigator.navigateToSiteHome({ params: redirectData, preferCurrentTab: false, siteId });
+ }
+
+}
diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts
index 9be527c82..23ee93791 100644
--- a/src/core/features/login/services/login-helper.ts
+++ b/src/core/features/login/services/login-helper.ts
@@ -412,22 +412,19 @@ export class CoreLoginHelperProvider {
* @returns Promise resolved when done.
*/
async goToAddSite(setRoot = false, showKeyboard = false): Promise {
- let path = '/login/sites';
- let params: Params = { openAddSite: true , showKeyboard };
-
if (CoreSites.isLoggedIn()) {
- const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
- redirectPath: path,
- redirectOptions: { params },
+ // Logout first.
+ await CoreSites.logout({
+ siteId: CoreConstants.NO_SITE_ID,
+ redirectPath: '/login/sites',
+ redirectOptions: { params: { openAddSite: true , showKeyboard } },
});
- if (willReload) {
- return;
- }
- } else {
- [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
+ return;
}
+ const [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
+
await CoreNavigator.navigate(path, { params, reset: setRoot });
}
diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.ts b/src/core/features/mainmenu/components/user-menu/user-menu.ts
index e52071150..1aecec73f 100644
--- a/src/core/features/mainmenu/components/user-menu/user-menu.ts
+++ b/src/core/features/mainmenu/components/user-menu/user-menu.ts
@@ -18,7 +18,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { CoreSite } from '@classes/sites/site';
import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
import { CoreFilter } from '@features/filter/services/filter';
-import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
import { CoreUserSupport } from '@features/user/services/support';
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
@@ -33,8 +32,9 @@ import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CorePromiseUtils } from '@singletons/promise-utils';
-import { ModalController, Translate } from '@singletons';
+import { ModalController } from '@singletons';
import { Subscription } from 'rxjs';
+import { CoreLoginHelper } from '@features/login/services/login-helper';
/**
* Component to display a user menu.
@@ -208,12 +208,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
* @param event Click event
*/
async logout(event: Event): Promise {
- if (CoreNavigator.currentRouteCanBlockLeave()) {
- await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
-
- return;
- }
-
if (this.removeAccountOnLogout) {
// Ask confirm.
const siteName = this.siteName ?
@@ -242,12 +236,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
* @param event Click event
*/
async switchAccounts(event: Event): Promise {
- if (CoreNavigator.currentRouteCanBlockLeave()) {
- await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
-
- return;
- }
-
const thisModal = await ModalController.getTop();
event.preventDefault();
diff --git a/src/core/features/policy/pages/site-policy/site-policy.ts b/src/core/features/policy/pages/site-policy/site-policy.ts
index 94be08c61..dfab2c14f 100644
--- a/src/core/features/policy/pages/site-policy/site-policy.ts
+++ b/src/core/features/policy/pages/site-policy/site-policy.ts
@@ -280,9 +280,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy {
* @returns Promise resolved when done.
*/
async cancel(): Promise {
- await CorePromiseUtils.ignoreErrors(CoreSites.logout());
-
- await CoreNavigator.navigate('/login/sites', { reset: true });
+ await CoreSites.logout();
}
/**
diff --git a/src/core/features/sitehome/tests/links.test.ts b/src/core/features/sitehome/tests/links.test.ts
index f137e69ec..cf4b21326 100644
--- a/src/core/features/sitehome/tests/links.test.ts
+++ b/src/core/features/sitehome/tests/links.test.ts
@@ -32,6 +32,7 @@ describe('Site Home link handlers', () => {
isStoredRootURL: () => Promise.resolve({ siteIds: [siteId] }),
getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')),
getSiteIdsFromUrl: () => Promise.resolve([siteId]),
+ getCurrentSiteId: () => siteId,
}));
mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] });
diff --git a/src/core/services/navigator.ts b/src/core/services/navigator.ts
index 6632359f7..2d07de2d3 100644
--- a/src/core/services/navigator.ts
+++ b/src/core/services/navigator.ts
@@ -220,14 +220,13 @@ export class CoreNavigatorService {
// If we are logged into a different site, log out first.
if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
- const willReload = await CoreSites.logoutForRedirect(siteId, {
+ await CoreSites.logout({
redirectPath: path,
redirectOptions: options || {},
+ siteId,
});
- if (willReload) {
- return true;
- }
+ return true;
}
// If the path doesn't belong to a site, call standard navigation.
diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts
index 1dfdd725e..b761cc9f6 100644
--- a/src/core/services/sites.ts
+++ b/src/core/services/sites.ts
@@ -141,14 +141,6 @@ export class CoreSitesProvider {
// Remove version classes from body.
CoreHTMLClasses.removeSiteClasses();
-
- // Go to sites page when user is logged out.
- await CoreNavigator.navigate('/login/sites', { reset: true });
-
- if (CoreSitePlugins.hasSitePluginsLoaded) {
- // Temporary fix. Reload the page to unload all plugins.
- window.location.reload();
- }
});
CoreEvents.on(CoreEvents.LOGIN, async (data) => {
@@ -964,7 +956,7 @@ export class CoreSitesProvider {
promise.finally(() => {
if (siteId) {
// Logout the currentSite and expire the token.
- this.logout();
+ this.internalLogout();
this.setSiteLoggedOut(siteId);
}
});
@@ -1123,7 +1115,7 @@ export class CoreSitesProvider {
this.logger.debug(`Delete site ${siteId}`);
if (this.currentSite !== undefined && this.currentSite.id == siteId) {
- this.logout();
+ this.internalLogout();
}
const site = await this.getSite(siteId);
@@ -1457,10 +1449,23 @@ export class CoreSitesProvider {
/**
* Logout the user.
*
- * @param options Logout options.
- * @returns Promise resolved when the user is logged out.
+ * @param options Options.
*/
async logout(options: CoreSitesLogoutOptions = {}): Promise {
+ await CoreNavigator.navigate('/logout', {
+ params: { ...options },
+ reset: true,
+ });
+ }
+
+ /**
+ * Logout the user.
+ * This function is for internal usage, please use CoreSites.logout instead. The reason this function is public is because
+ * it's called from the CoreLoginLogoutPage page.
+ *
+ * @param options Logout options.
+ */
+ async internalLogout(options: InternalLogoutOptions = {}): Promise {
if (!this.currentSite) {
return;
}
@@ -1494,6 +1499,7 @@ export class CoreSitesProvider {
* @param siteId Site that will be opened after logout.
* @param redirectData Page/url to open after logout.
* @returns Promise resolved with boolean: true if app will be reloaded after logout.
+ * @deprecated since 5.0. Use CoreSites.logout instead, it automatically handles redirects.
*/
async logoutForRedirect(siteId: string, redirectData: CoreRedirectPayload): Promise {
if (!this.currentSite) {
@@ -1505,7 +1511,7 @@ export class CoreSitesProvider {
CoreRedirects.storeRedirect(siteId, redirectData);
}
- await this.logout();
+ await this.internalLogout();
return CoreSitePlugins.hasSitePluginsLoaded;
}
@@ -2480,7 +2486,14 @@ export type CoreSitesLoginTokenResponse = {
/**
* Options for logout.
*/
-export type CoreSitesLogoutOptions = {
+export type CoreSitesLogoutOptions = CoreRedirectPayload & InternalLogoutOptions & {
+ siteId?: string; // Site ID to load after logout.
+};
+
+/**
+ * Options for internal logout.
+ */
+type InternalLogoutOptions = {
forceLogout?: boolean; // If true, site will be marked as logged out, no matter the value tool_mobile_forcelogout.
removeAccount?: boolean; // If true, site will be removed too after logout.
};
diff --git a/src/core/services/tests/sites.test.ts b/src/core/services/tests/sites.test.ts
index 68e477405..961c84708 100644
--- a/src/core/services/tests/sites.test.ts
+++ b/src/core/services/tests/sites.test.ts
@@ -16,7 +16,6 @@ import { CoreEvents } from '@singletons/events';
import { CoreLang, CoreLangProvider } from '@services/lang';
import { mock, mockSingleton } from '@/testing/utils';
-import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { Http } from '@singletons';
import { of } from 'rxjs';
@@ -34,13 +33,10 @@ describe('CoreSitesProvider', () => {
});
it('cleans up on logout', async () => {
- const navigator: CoreNavigatorService = mockSingleton(CoreNavigator, ['navigate']);
-
CoreSites.initialize();
CoreEvents.trigger(CoreEvents.LOGOUT);
expect(langProvider.clearCustomStrings).toHaveBeenCalled();
- expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
});
it('adds ionic platform and theme classes', async () => {
diff --git a/src/core/services/urlschemes.ts b/src/core/services/urlschemes.ts
index 65cd87bc4..833bdcc8e 100644
--- a/src/core/services/urlschemes.ts
+++ b/src/core/services/urlschemes.ts
@@ -432,14 +432,13 @@ export class CoreCustomURLSchemesProvider {
// Ask the user before changing site.
await CoreDomUtils.showConfirm(Translate.instant('core.contentlinks.confirmurlothersite'));
- const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
+ await CoreSites.logout({
+ siteId: CoreConstants.NO_SITE_ID,
redirectPath: '/login/credentials',
redirectOptions: { params: pageParams },
});
- if (willReload) {
- return;
- }
+ return;
}
await CoreNavigator.navigateToLoginCredentials(pageParams);
diff --git a/upgrade.txt b/upgrade.txt
index edb6425e0..a9429f01f 100644
--- a/upgrade.txt
+++ b/upgrade.txt
@@ -2,6 +2,10 @@ This file describes API changes in the Moodle App that affect site plugins, info
For more information about upgrading, read the official documentation: https://moodledev.io/general/app/upgrading/
+=== 5.0.0 ===
+
+ - 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.
+
=== 4.5.0 ===
- Ionic has been upgraded to major version 8. See breaking changes and upgrade guide here: https://ionicframework.com/docs/updating/8-0