diff --git a/src/addon/mod/url/providers/helper.ts b/src/addon/mod/url/providers/helper.ts index 5a2c0dec9..4620fce98 100644 --- a/src/addon/mod/url/providers/helper.ts +++ b/src/addon/mod/url/providers/helper.ts @@ -33,7 +33,7 @@ export class AddonModUrlHelperProvider { */ open(url: string): void { const modal = this.domUtils.showModalLoading(); - this.contentLinksHelper.handleLink(url).then((treated) => { + this.contentLinksHelper.handleLink(url, undefined, undefined, true, true).then((treated) => { if (!treated) { return this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(url); } diff --git a/src/addon/mod/url/providers/module-handler.ts b/src/addon/mod/url/providers/module-handler.ts index 9cf493fd9..a4ba652a5 100644 --- a/src/addon/mod/url/providers/module-handler.ts +++ b/src/addon/mod/url/providers/module-handler.ts @@ -79,7 +79,7 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler { handler.courseProvider.loadModuleContents(module, courseId, undefined, false, false, undefined, handler.modName) .then(() => { // Check if the URL can be handled by the app. If so, always open it directly. - return handler.contentLinksHelper.canHandleLink(module.contents[0].fileurl, courseId); + return handler.contentLinksHelper.canHandleLink(module.contents[0].fileurl, courseId, undefined, true); }).then((canHandle) => { if (canHandle) { // URL handled by the app, open it directly. diff --git a/src/addon/notifications/providers/push-click-handler.ts b/src/addon/notifications/providers/push-click-handler.ts index 54178d1bf..ce39f5d17 100644 --- a/src/addon/notifications/providers/push-click-handler.ts +++ b/src/addon/notifications/providers/push-click-handler.ts @@ -65,7 +65,7 @@ export class AddonNotificationsPushClickHandler implements CorePushNotifications // Try to handle the appurl first. if (notification.customdata && notification.customdata.appurl) { - promise = this.linkHelper.handleLink(notification.customdata.appurl); + promise = this.linkHelper.handleLink(notification.customdata.appurl, undefined, undefined, true); } else { promise = Promise.resolve(false); } diff --git a/src/classes/site.ts b/src/classes/site.ts index 5331b38a1..446570344 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -1370,7 +1370,7 @@ export class CoreSite { return false; } - const siteUrl = this.urlUtils.removeProtocolAndWWW(this.siteUrl); + const siteUrl = this.textUtils.removeEndingSlash(this.urlUtils.removeProtocolAndWWW(this.siteUrl)); url = this.urlUtils.removeProtocolAndWWW(url); return url.indexOf(siteUrl) == 0; diff --git a/src/core/contentlinks/providers/helper.ts b/src/core/contentlinks/providers/helper.ts index 6b0dfa3ab..8347684c9 100644 --- a/src/core/contentlinks/providers/helper.ts +++ b/src/core/contentlinks/providers/helper.ts @@ -29,6 +29,7 @@ import { CoreContentLinksDelegate, CoreContentLinksAction } from './delegate'; import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../../../configconstants'; import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins'; +import { CoreSite } from '@classes/site'; /** * Service that provides some features regarding content links. @@ -54,11 +55,27 @@ export class CoreContentLinksHelperProvider { * @param {string} url URL to handle. * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @param {string} [username] Username to use to filter sites. + * @param {boolean} [checkRoot] Whether to check if the URL is the root URL of a site. * @return {Promise} Promise resolved with a boolean: whether the URL can be handled. */ - canHandleLink(url: string, courseId?: number, username?: string): Promise { - return this.contentLinksDelegate.getActionsFor(url, undefined, username).then((actions) => { - return !!this.getFirstValidAction(actions); + canHandleLink(url: string, courseId?: number, username?: string, checkRoot?: boolean): Promise { + let promise; + + if (checkRoot) { + promise = this.isStoredRootURL(url, username); + } else { + promise = Promise.resolve({}); + } + + return promise.then((data) => { + if (data.site) { + // URL is the root of the site, can handle it. + return true; + } + + return this.contentLinksDelegate.getActionsFor(url, undefined, username).then((actions) => { + return !!this.getFirstValidAction(actions); + }); }).catch(() => { return false; }); @@ -148,10 +165,16 @@ export class CoreContentLinksHelperProvider { // Wait for the app to be ready. this.initDelegate.ready().then(() => { - // Check if the site is stored. - return this.sitesProvider.getSiteIdsFromUrl(url, false, username); - }).then((siteIds) => { - if (siteIds.length) { + // Check if it's the root URL. + return this.isStoredRootURL(url, username); + }).then((data) => { + + if (data.site) { + // Root URL. + modal.dismiss(); + + return this.handleRootURL(data.site, false); + } else if (data.hasSites) { modal.dismiss(); // Dismiss modal so it doesn't collide with confirms. return this.handleLink(url, username).then((treated) => { @@ -161,11 +184,13 @@ export class CoreContentLinksHelperProvider { }); } else { // Get the site URL. - const siteUrl = this.contentLinksDelegate.getSiteUrl(url); - if (!siteUrl) { - this.domUtils.showErrorModal('core.login.invalidsite', true); + let siteUrl = this.contentLinksDelegate.getSiteUrl(url), + urlToOpen = url; - return; + if (!siteUrl) { + // Site URL not found, use the original URL since it could be the root URL of the site. + siteUrl = url; + urlToOpen = undefined; } // Check that site exists. @@ -176,7 +201,7 @@ export class CoreContentLinksHelperProvider { pageParams = { siteUrl: result.siteUrl, username: username, - urlToOpen: url, + urlToOpen: urlToOpen, siteConfig: result.config }; let promise, @@ -210,14 +235,12 @@ export class CoreContentLinksHelperProvider { this.loginHelper.confirmAndOpenBrowserForSSOLogin( result.siteUrl, result.code, result.service, result.config && result.config.launchurl); } else if (!hasSitePluginsLoaded) { - this.appProvider.getRootNavController().setRoot(pageName, pageParams); + return this.loginHelper.goToNoSitePage(undefined, pageName, pageParams); } }); }).catch((error) => { - if (error) { - this.domUtils.showErrorModal(error); - } + this.domUtils.showErrorModalDefault(error, this.translate.instant('core.login.invalidsite')); }); } }).finally(() => { @@ -234,42 +257,119 @@ export class CoreContentLinksHelperProvider { * @param {string} [username] Username related with the URL. E.g. in 'http://myuser@m.com', url would be 'http://m.com' and * the username 'myuser'. Don't use it if you don't want to filter by username. * @param {NavController} [navCtrl] Nav Controller to use to navigate. + * @param {boolean} [checkRoot] Whether to check if the URL is the root URL of a site. + * @param {boolean} [openBrowserRoot] Whether to open in browser if it's root URL and it belongs to current site. * @return {Promise} Promise resolved with a boolean: true if URL was treated, false otherwise. */ - handleLink(url: string, username?: string, navCtrl?: NavController): Promise { - // Check if the link should be treated by some component/addon. - return this.contentLinksDelegate.getActionsFor(url, undefined, username).then((actions) => { - const action = this.getFirstValidAction(actions); - if (action) { - if (!this.sitesProvider.isLoggedIn()) { - // No current site. Perform the action if only 1 site found, choose the site otherwise. - if (action.sites.length == 1) { - action.action(action.sites[0], navCtrl); - } else { - this.goToChooseSite(url); - } - } else if (action.sites.length == 1 && action.sites[0] == this.sitesProvider.getCurrentSiteId()) { - // Current site. - action.action(action.sites[0], navCtrl); - } else { - // Not current site or more than one site. Ask for confirmation. - this.domUtils.showConfirm(this.translate.instant('core.contentlinks.confirmurlothersite')).then(() => { + handleLink(url: string, username?: string, navCtrl?: NavController, checkRoot?: boolean, openBrowserRoot?: boolean) + : Promise { + let promise; + + if (checkRoot) { + promise = this.isStoredRootURL(url, username); + } else { + promise = Promise.resolve({}); + } + + return promise.then((data) => { + if (data.site) { + // URL is the root of the site. + this.handleRootURL(data.site, openBrowserRoot); + + return true; + } + + // Check if the link should be treated by some component/addon. + return this.contentLinksDelegate.getActionsFor(url, undefined, username).then((actions) => { + const action = this.getFirstValidAction(actions); + if (action) { + if (!this.sitesProvider.isLoggedIn()) { + // No current site. Perform the action if only 1 site found, choose the site otherwise. if (action.sites.length == 1) { action.action(action.sites[0], navCtrl); } else { this.goToChooseSite(url); } - }).catch(() => { - // User canceled. - }); + } else if (action.sites.length == 1 && action.sites[0] == this.sitesProvider.getCurrentSiteId()) { + // Current site. + action.action(action.sites[0], navCtrl); + } else { + // Not current site or more than one site. Ask for confirmation. + this.domUtils.showConfirm(this.translate.instant('core.contentlinks.confirmurlothersite')).then(() => { + if (action.sites.length == 1) { + action.action(action.sites[0], navCtrl); + } else { + this.goToChooseSite(url); + } + }).catch(() => { + // User canceled. + }); + } + + return true; } - return true; + return false; + }).catch(() => { + return false; + }); + }); + } + + /** + * Handle a root URL of a site. + * + * @param {CoreSite} site Site to handle. + * @param {boolean} [openBrowserRoot] Whether to open in browser if it's root URL and it belongs to current site. + * @return {Promise} Promise resolved when done. + */ + handleRootURL(site: CoreSite, openBrowserRoot?: boolean): Promise { + const currentSite = this.sitesProvider.getCurrentSite(); + + if (currentSite && currentSite.getURL() == site.getURL()) { + // Already logged in. + if (openBrowserRoot) { + return site.openInBrowserWithAutoLogin(site.getURL()); } - return false; - }).catch(() => { - return false; + return Promise.resolve(); + } else { + // Login in the site. + return this.loginHelper.redirect('', {}, site.getId()); + } + } + + /** + * Check if a URL is the root URL of any of the stored sites. If so, return the site ID. + * + * @param {string} url URL to check. + * @param {string} username Username to check. + * @return {Promise<{site: CoreSite, hasSites: boolean}>} Promise resolved with site and whether there is any site to treat + * the URL. Site will be undefined if it isn't the root URL of any stored site. + */ + isStoredRootURL(url: string, username: string): Promise<{site: CoreSite, hasSites: boolean}> { + // Check if the site is stored. + return this.sitesProvider.getSiteIdsFromUrl(url, true, username).then((siteIds) => { + const result = { + hasSites: siteIds.length > 0, + site: undefined + }; + + if (result.hasSites) { + // If more than one site is returned it usually means there are different users stored. Use any of them. + return this.sitesProvider.getSite(siteIds[0]).then((site) => { + const siteUrl = this.textUtils.removeEndingSlash(this.urlUtils.removeProtocolAndWWW(site.getURL())), + treatedUrl = this.textUtils.removeEndingSlash(this.urlUtils.removeProtocolAndWWW(url)); + + if (siteUrl == treatedUrl) { + result.site = site; + } + + return result; + }); + } + + return result; }); } } diff --git a/src/core/login/pages/init/init.ts b/src/core/login/pages/init/init.ts index 3758d04c3..a33c0b2d5 100644 --- a/src/core/login/pages/init/init.ts +++ b/src/core/login/pages/init/init.ts @@ -43,7 +43,7 @@ export class CoreLoginInitPage { this.initDelegate.ready().then(() => { // Check if there was a pending redirect. const redirectData = this.appProvider.getRedirect(); - if (redirectData.siteId && redirectData.page) { + if (redirectData.siteId) { // Unset redirect data. this.appProvider.storeRedirect('', '', ''); @@ -63,8 +63,8 @@ export class CoreLoginInitPage { return this.loadPage(); }); } else { - // No site to load, just open the state. - return this.navCtrl.setRoot(redirectData.page, redirectData.params, { animate: false }); + // No site to load, open the page. + return this.loginHelper.goToNoSitePage(this.navCtrl, redirectData.page, redirectData.params); } } } diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index 679d9cbae..4967148dd 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -416,6 +416,43 @@ export class CoreLoginHelperProvider { } } + /** + * Open a page that doesn't belong to any site. + * + * @param {NavController} [navCtrl] Nav Controller. + * @param {string} [page] Page to open. + * @param {any} [params] Params of the page. + * @return {Promise} Promise resolved when done. + */ + goToNoSitePage(navCtrl: NavController, page: string, params?: any): Promise { + navCtrl = navCtrl || this.appProvider.getRootNavController(); + + if (page == 'CoreLoginSitesPage') { + // Just open the page as root. + return navCtrl.setRoot(page, params); + } else { + // Check if there is any site stored. + return this.sitesProvider.hasSites().then((hasSites) => { + if (hasSites) { + // There are sites stored, open sites page first to be able to go back. + navCtrl.setRoot('CoreLoginSitesPage'); + + return navCtrl.push(page, page, {animate: false}); + } else { + if (page != 'CoreLoginSitePage') { + // Open the new site page to be able to go back. + navCtrl.setRoot('CoreLoginSitePage'); + + return navCtrl.push(page, page, {animate: false}); + } else { + // Just open the page as root. + return navCtrl.setRoot(page, params); + } + } + }); + } + } + /** * Go to the initial page of a site depending on 'userhomepage' setting. * diff --git a/src/directives/link.ts b/src/directives/link.ts index 5746fbf51..0540eb83f 100644 --- a/src/directives/link.ts +++ b/src/directives/link.ts @@ -68,7 +68,7 @@ export class CoreLinkDirective implements OnInit { event.stopPropagation(); if (this.utils.isTrueOrOne(this.capture)) { - this.contentLinksHelper.handleLink(href, undefined, navCtrl).then((treated) => { + this.contentLinksHelper.handleLink(href, undefined, navCtrl, true, true).then((treated) => { if (!treated) { this.navigate(href); } diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index 7c30d9ec9..a21ba8838 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -551,6 +551,24 @@ export class CoreTextUtilsProvider { return typeof defaultValue != 'undefined' ? defaultValue : json; } + /** + * Remove ending slash from a path or URL. + * + * @param {string} text Text to treat. + * @return {string} Treated text. + */ + removeEndingSlash(text: string): string { + if (!text) { + return ''; + } + + if (text.slice(-1) == '/') { + return text.substr(0, text.length - 1); + } + + return text; + } + /** * Replace all characters that cause problems with files in Android and iOS. *