commit
						b9d8d22b79
					
				| @ -1264,6 +1264,7 @@ | ||||
|   "core.contentlinks.confirmurlothersite": "local_moodlemobileapp", | ||||
|   "core.contentlinks.errornoactions": "local_moodlemobileapp", | ||||
|   "core.contentlinks.errornosites": "local_moodlemobileapp", | ||||
|   "core.contentlinks.errorredirectothersite": "local_moodlemobileapp", | ||||
|   "core.continue": "moodle", | ||||
|   "core.copiedtoclipboard": "local_moodlemobileapp", | ||||
|   "core.course": "moodle", | ||||
|  | ||||
| @ -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); | ||||
|             } | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
| @ -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); | ||||
|         } | ||||
|  | ||||
| @ -19,6 +19,9 @@ import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreLangProvider } from '@providers/lang'; | ||||
| import { CoreLoggerProvider } from '@providers/logger'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreUrlUtilsProvider } from '@providers/utils/url'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreCustomURLSchemesProvider } from '@providers/urlschemes'; | ||||
| import { CoreLoginHelperProvider } from '@core/login/providers/helper'; | ||||
| import { Keyboard } from '@ionic-native/keyboard'; | ||||
| import { ScreenOrientation } from '@ionic-native/screen-orientation'; | ||||
| @ -32,11 +35,13 @@ export class MoodleMobileApp implements OnInit { | ||||
|     rootPage: any = 'CoreLoginInitPage'; | ||||
|     protected logger; | ||||
|     protected lastUrls = {}; | ||||
|     protected lastInAppUrl: string; | ||||
| 
 | ||||
|     constructor(private platform: Platform, logger: CoreLoggerProvider, keyboard: Keyboard, | ||||
|             private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider, private zone: NgZone, | ||||
|             private appProvider: CoreAppProvider, private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider, | ||||
|             private screenOrientation: ScreenOrientation, app: IonicApp) { | ||||
|             private screenOrientation: ScreenOrientation, app: IonicApp, private urlSchemesProvider: CoreCustomURLSchemesProvider, | ||||
|             private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider) { | ||||
|         this.logger = logger.getInstance('AppComponent'); | ||||
| 
 | ||||
|         platform.ready().then(() => { | ||||
| @ -98,13 +103,39 @@ export class MoodleMobileApp implements OnInit { | ||||
| 
 | ||||
|         // Check URLs loaded in any InAppBrowser.
 | ||||
|         this.eventsProvider.on(CoreEventsProvider.IAB_LOAD_START, (event) => { | ||||
|             this.loginHelper.inAppBrowserLoadStart(event.url); | ||||
|             // URLs with a custom scheme can be prefixed with "http://" or "https://", we need to remove this.
 | ||||
|             const url = event.url.replace(/^https?:\/\//, ''); | ||||
| 
 | ||||
|             if (this.urlSchemesProvider.isCustomURL(url)) { | ||||
|                 // Close the browser if it's a valid SSO URL.
 | ||||
|                 this.urlSchemesProvider.handleCustomURL(url); | ||||
|                 this.utils.closeInAppBrowser(false); | ||||
| 
 | ||||
|             } else if (this.platform.is('android')) { | ||||
|                 // Check if the URL has a custom URL scheme. In Android they need to be opened manually.
 | ||||
|                 const urlScheme = this.urlUtils.getUrlProtocol(url); | ||||
|                 if (urlScheme && urlScheme !== 'file' && urlScheme !== 'cdvfile') { | ||||
|                     // Open in browser should launch the right app if found and do nothing if not found.
 | ||||
|                     this.utils.openInBrowser(url); | ||||
| 
 | ||||
|                     // At this point the InAppBrowser is showing a "Webpage not available" error message.
 | ||||
|                     // Try to navigate to last loaded URL so this error message isn't found.
 | ||||
|                     if (this.lastInAppUrl) { | ||||
|                         this.utils.openInApp(this.lastInAppUrl); | ||||
|                     } else { | ||||
|                         // No last URL loaded, close the InAppBrowser.
 | ||||
|                         this.utils.closeInAppBrowser(false); | ||||
|                     } | ||||
|                 } else { | ||||
|                     this.lastInAppUrl = url; | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         // Check InAppBrowser closed.
 | ||||
|         this.eventsProvider.on(CoreEventsProvider.IAB_EXIT, () => { | ||||
|             this.loginHelper.waitingForBrowser = false; | ||||
|             this.loginHelper.lastInAppUrl = ''; | ||||
|             this.lastInAppUrl = ''; | ||||
|             this.loginHelper.checkLogout(); | ||||
|         }); | ||||
| 
 | ||||
| @ -131,14 +162,10 @@ export class MoodleMobileApp implements OnInit { | ||||
|                 this.lastUrls[url] = Date.now(); | ||||
| 
 | ||||
|                 this.eventsProvider.trigger(CoreEventsProvider.APP_LAUNCHED_URL, url); | ||||
|                 this.urlSchemesProvider.handleCustomURL(url); | ||||
|             }); | ||||
|         }; | ||||
| 
 | ||||
|         // Listen for app launched URLs. If we receive one, check if it's a SSO authentication.
 | ||||
|         this.eventsProvider.on(CoreEventsProvider.APP_LAUNCHED_URL, (url) => { | ||||
|             this.loginHelper.appLaunchedByURL(url); | ||||
|         }); | ||||
| 
 | ||||
|         // Load custom lang strings. This cannot be done inside the lang provider because it causes circular dependencies.
 | ||||
|         const loadCustomStrings = (): void => { | ||||
|             const currentSite = this.sitesProvider.getCurrentSite(), | ||||
|  | ||||
| @ -59,6 +59,7 @@ import { CoreUpdateManagerProvider } from '@providers/update-manager'; | ||||
| import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; | ||||
| import { CoreSyncProvider } from '@providers/sync'; | ||||
| import { CoreFileHelperProvider } from '@providers/file-helper'; | ||||
| import { CoreCustomURLSchemesProvider } from '@providers/urlschemes'; | ||||
| 
 | ||||
| // Core modules.
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| @ -161,7 +162,8 @@ export const CORE_PROVIDERS: any[] = [ | ||||
|     CoreUpdateManagerProvider, | ||||
|     CorePluginFileDelegate, | ||||
|     CoreSyncProvider, | ||||
|     CoreFileHelperProvider | ||||
|     CoreFileHelperProvider, | ||||
|     CoreCustomURLSchemesProvider | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
| @ -280,6 +282,7 @@ export const CORE_PROVIDERS: any[] = [ | ||||
|         CorePluginFileDelegate, | ||||
|         CoreSyncProvider, | ||||
|         CoreFileHelperProvider, | ||||
|         CoreCustomURLSchemesProvider, | ||||
|         { | ||||
|             provide: HTTP_INTERCEPTORS, | ||||
|             useClass: CoreInterceptor, | ||||
|  | ||||
| @ -1264,6 +1264,7 @@ | ||||
|     "core.contentlinks.confirmurlothersite": "This link belongs to another site. Do you want to open it?", | ||||
|     "core.contentlinks.errornoactions": "Couldn't find an action to perform with this link.", | ||||
|     "core.contentlinks.errornosites": "Couldn't find any site to handle this link.", | ||||
|     "core.contentlinks.errorredirectothersite": "The redirect URL cannot point to a different site.", | ||||
|     "core.continue": "Continue", | ||||
|     "core.copiedtoclipboard": "Text copied to clipboard", | ||||
|     "core.course": "Course", | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -3,5 +3,6 @@ | ||||
|     "chooseaccounttoopenlink": "Choose an account to open the link with.", | ||||
|     "confirmurlothersite": "This link belongs to another site. Do you want to open it?", | ||||
|     "errornoactions": "Couldn't find an action to perform with this link.", | ||||
|     "errornosites": "Couldn't find any site to handle this link." | ||||
|     "errornosites": "Couldn't find any site to handle this link.", | ||||
|     "errorredirectothersite": "The redirect URL cannot point to a different site." | ||||
| } | ||||
| @ -14,10 +14,12 @@ | ||||
| 
 | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { IonicPage, NavController, NavParams } from 'ionic-angular'; | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreContentLinksDelegate, CoreContentLinksAction } from '../../providers/delegate'; | ||||
| import { CoreContentLinksHelperProvider } from '../../providers/helper'; | ||||
| import { CoreLoginHelperProvider } from '@core/login/providers/helper'; | ||||
| 
 | ||||
| /** | ||||
|  * Page to display the list of sites to choose one to perform a content link action. | ||||
| @ -33,10 +35,11 @@ export class CoreContentLinksChooseSitePage implements OnInit { | ||||
|     sites: any[]; | ||||
|     loaded: boolean; | ||||
|     protected action: CoreContentLinksAction; | ||||
|     protected isRootURL: boolean; | ||||
| 
 | ||||
|     constructor(private navCtrl: NavController, navParams: NavParams, private contentLinksDelegate: CoreContentLinksDelegate, | ||||
|             private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, | ||||
|             private contentLinksHelper: CoreContentLinksHelperProvider) { | ||||
|             private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, private translate: TranslateService, | ||||
|             private contentLinksHelper: CoreContentLinksHelperProvider, private loginHelper: CoreLoginHelperProvider) { | ||||
|         this.url = navParams.get('url'); | ||||
|     } | ||||
| 
 | ||||
| @ -48,19 +51,35 @@ export class CoreContentLinksChooseSitePage implements OnInit { | ||||
|             return this.leaveView(); | ||||
|         } | ||||
| 
 | ||||
|         // Get the action to perform.
 | ||||
|         this.contentLinksDelegate.getActionsFor(this.url).then((actions) => { | ||||
|         // Check if it's the root URL.
 | ||||
|         this.sitesProvider.isStoredRootURL(this.url).then((data): any => { | ||||
|             if (data.site) { | ||||
|                 // It's the root URL.
 | ||||
|                 this.isRootURL = true; | ||||
| 
 | ||||
|                 return data.siteIds; | ||||
|             } else if (data.siteIds.length) { | ||||
|                 // Not root URL, but the URL belongs to at least 1 site. Check if there is any action to treat the link.
 | ||||
|                 return this.contentLinksDelegate.getActionsFor(this.url).then((actions): any => { | ||||
|                     this.action = this.contentLinksHelper.getFirstValidAction(actions); | ||||
|                     if (!this.action) { | ||||
|                 return Promise.reject(null); | ||||
|                         return Promise.reject(this.translate.instant('core.contentlinks.errornoactions')); | ||||
|                     } | ||||
| 
 | ||||
|             // Get the sites that can perform the action.
 | ||||
|             return this.sitesProvider.getSites(this.action.sites).then((sites) => { | ||||
|                 this.sites = sites; | ||||
|                     return this.action.sites; | ||||
|                 }); | ||||
|         }).catch(() => { | ||||
|             this.domUtils.showErrorModal('core.contentlinks.errornosites', true); | ||||
|             } else { | ||||
|                 // No sites to treat the URL.
 | ||||
|                 return Promise.reject(this.translate.instant('core.contentlinks.errornosites')); | ||||
|             } | ||||
|         }).then((siteIds) => { | ||||
|             // Get the sites that can perform the action.
 | ||||
|             return this.sitesProvider.getSites(siteIds); | ||||
|         }).then((sites) => { | ||||
|             this.sites = sites; | ||||
| 
 | ||||
|         }).catch((error) => { | ||||
|             this.domUtils.showErrorModalDefault(error, 'core.contentlinks.errornosites', true); | ||||
|             this.leaveView(); | ||||
|         }).finally(() => { | ||||
|             this.loaded = true; | ||||
| @ -80,8 +99,12 @@ export class CoreContentLinksChooseSitePage implements OnInit { | ||||
|      * @param {string} siteId Site ID. | ||||
|      */ | ||||
|     siteClicked(siteId: string): void { | ||||
|         if (this.isRootURL) { | ||||
|             this.loginHelper.redirect('', {}, siteId); | ||||
|         } else { | ||||
|             this.action.action(siteId, this.navCtrl); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Cancel and leave the view. | ||||
|  | ||||
| @ -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. | ||||
| @ -43,9 +44,6 @@ export class CoreContentLinksHelperProvider { | ||||
|             private initDelegate: CoreInitDelegate, eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider, | ||||
|             private sitePluginsProvider: CoreSitePluginsProvider, private zone: NgZone, private utils: CoreUtilsProvider) { | ||||
|         this.logger = logger.getInstance('CoreContentLinksHelperProvider'); | ||||
| 
 | ||||
|         // Listen for app launched URLs. If we receive one, check if it's a content link.
 | ||||
|         eventsProvider.on(CoreEventsProvider.APP_LAUNCHED_URL, this.handleCustomUrl.bind(this)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -54,11 +52,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<boolean>} Promise resolved with a boolean: whether the URL can be handled. | ||||
|      */ | ||||
|     canHandleLink(url: string, courseId?: number, username?: string): Promise<boolean> { | ||||
|     canHandleLink(url: string, courseId?: number, username?: string, checkRoot?: boolean): Promise<boolean> { | ||||
|         let promise; | ||||
| 
 | ||||
|         if (checkRoot) { | ||||
|             promise = this.sitesProvider.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; | ||||
|         }); | ||||
| @ -122,6 +136,7 @@ export class CoreContentLinksHelperProvider { | ||||
|      * | ||||
|      * @param {string} url URL to handle. | ||||
|      * @return {boolean} True if the URL should be handled by this component, false otherwise. | ||||
|      * @deprecated Please use CoreCustomURLSchemesProvider.handleCustomURL instead. | ||||
|      */ | ||||
|     handleCustomUrl(url: string): boolean { | ||||
|         const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link'; | ||||
| @ -148,10 +163,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.sitesProvider.isStoredRootURL(url, username); | ||||
|         }).then((data) => { | ||||
| 
 | ||||
|             if (data.site) { | ||||
|                 // Root URL.
 | ||||
|                 modal.dismiss(); | ||||
| 
 | ||||
|                 return this.handleRootURL(data.site, false); | ||||
|             } else if (data.siteIds.length > 0) { | ||||
|                 modal.dismiss(); // Dismiss modal so it doesn't collide with confirms.
 | ||||
| 
 | ||||
|                 return this.handleLink(url, username).then((treated) => { | ||||
| @ -161,11 +182,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 +199,7 @@ export class CoreContentLinksHelperProvider { | ||||
|                         pageParams = { | ||||
|                             siteUrl: result.siteUrl, | ||||
|                             username: username, | ||||
|                             urlToOpen: url, | ||||
|                             urlToOpen: urlToOpen, | ||||
|                             siteConfig: result.config | ||||
|                         }; | ||||
|                     let promise, | ||||
| @ -210,14 +233,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,9 +255,28 @@ 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<boolean>} Promise resolved with a boolean: true if URL was treated, false otherwise. | ||||
|      */ | ||||
|     handleLink(url: string, username?: string, navCtrl?: NavController): Promise<boolean> { | ||||
|     handleLink(url: string, username?: string, navCtrl?: NavController, checkRoot?: boolean, openBrowserRoot?: boolean) | ||||
|             : Promise<boolean> { | ||||
|         let promise; | ||||
| 
 | ||||
|         if (checkRoot) { | ||||
|             promise = this.sitesProvider.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); | ||||
| @ -271,5 +311,31 @@ export class CoreContentLinksHelperProvider { | ||||
|             }).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. | ||||
|      * @param {boolean} [checkToken] Whether to check that token is the same to verify it's current site. If false or not defined, | ||||
|      *                               only the URL will be checked. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     handleRootURL(site: CoreSite, openBrowserRoot?: boolean, checkToken?: boolean): Promise<any> { | ||||
|         const currentSite = this.sitesProvider.getCurrentSite(); | ||||
| 
 | ||||
|         if (currentSite && currentSite.getURL() == site.getURL() && (!checkToken || currentSite.getToken() == site.getToken())) { | ||||
|             // Already logged in.
 | ||||
|             if (openBrowserRoot) { | ||||
|                 return site.openInBrowserWithAutoLogin(site.getURL()); | ||||
|             } | ||||
| 
 | ||||
|             return Promise.resolve(); | ||||
|         } else { | ||||
|             // Login in the site.
 | ||||
|             return this.loginHelper.redirect('', {}, site.getId()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -21,8 +21,6 @@ import { CoreSitesProvider } from '@providers/sites'; | ||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { CoreLoginHelperProvider } from '../../providers/helper'; | ||||
| import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; | ||||
| import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; | ||||
| import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | ||||
| import { CoreConfigConstants } from '../../../../configconstants'; | ||||
| 
 | ||||
| @ -56,8 +54,7 @@ export class CoreLoginCredentialsPage { | ||||
|     constructor(private navCtrl: NavController, navParams: NavParams, fb: FormBuilder, private appProvider: CoreAppProvider, | ||||
|             private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, | ||||
|             private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider, | ||||
|             private eventsProvider: CoreEventsProvider, private contentLinksDelegate: CoreContentLinksDelegate, | ||||
|             private contentLinksHelper: CoreContentLinksHelperProvider) { | ||||
|             private eventsProvider: CoreEventsProvider) { | ||||
| 
 | ||||
|         this.siteUrl = navParams.get('siteUrl'); | ||||
|         this.siteConfig = navParams.get('siteConfig'); | ||||
| @ -220,20 +217,7 @@ export class CoreLoginCredentialsPage { | ||||
| 
 | ||||
|                 this.siteId = id; | ||||
| 
 | ||||
|                 if (this.urlToOpen) { | ||||
|                     // There's a content link to open.
 | ||||
|                     return this.contentLinksDelegate.getActionsFor(this.urlToOpen, undefined, username).then((actions) => { | ||||
|                         const action = this.contentLinksHelper.getFirstValidAction(actions); | ||||
|                         if (action && action.sites.length) { | ||||
|                             // Action should only have 1 site because we're filtering by username.
 | ||||
|                             action.action(action.sites[0]); | ||||
|                         } else { | ||||
|                             return this.loginHelper.goToSiteInitialPage(); | ||||
|                         } | ||||
|                     }); | ||||
|                 } else { | ||||
|                     return this.loginHelper.goToSiteInitialPage(); | ||||
|                 } | ||||
|                 return this.loginHelper.goToSiteInitialPage(undefined, undefined, undefined, undefined, this.urlToOpen); | ||||
|             }); | ||||
|         }).catch((error) => { | ||||
|             this.loginHelper.treatUserTokenError(siteUrl, error, username, password); | ||||
|  | ||||
| @ -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); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @ -41,7 +41,7 @@ export interface CoreLoginSSOData { | ||||
|      * The site's URL. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     siteUrl?: string; | ||||
|     siteUrl: string; | ||||
| 
 | ||||
|     /** | ||||
|      * User's token. | ||||
| @ -78,7 +78,6 @@ export class CoreLoginHelperProvider { | ||||
|     protected logger; | ||||
|     protected isSSOConfirmShown = false; | ||||
|     protected isOpenEditAlertShown = false; | ||||
|     lastInAppUrl: string; | ||||
|     waitingForBrowser = false; | ||||
| 
 | ||||
|     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, | ||||
| @ -126,6 +125,7 @@ export class CoreLoginHelperProvider { | ||||
|      * | ||||
|      * @param {string} url URL received. | ||||
|      * @return {boolean} True if it's a SSO URL, false otherwise. | ||||
|      * @deprecated Please use CoreCustomURLSchemesProvider.handleCustomURL instead. | ||||
|      */ | ||||
|     appLaunchedByURL(url: string): boolean { | ||||
|         const ssoScheme = CoreConfigConstants.customurlscheme + '://token='; | ||||
| @ -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<any>} Promise resolved when done. | ||||
|      */ | ||||
|     goToNoSitePage(navCtrl: NavController, page: string, params?: any): Promise<any> { | ||||
|         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, params, {animate: false}); | ||||
|                 } else { | ||||
|                     if (page != 'CoreLoginSitePage') { | ||||
|                         // Open the new site page to be able to go back.
 | ||||
|                         navCtrl.setRoot('CoreLoginSitePage'); | ||||
| 
 | ||||
|                         return navCtrl.push(page, params, {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. | ||||
|      * | ||||
| @ -423,10 +460,11 @@ export class CoreLoginHelperProvider { | ||||
|      * @param {string} [page] Name of the page to load after loading the main page. | ||||
|      * @param {any} [params] Params to pass to the page. | ||||
|      * @param {NavOptions} [options] Navigation options. | ||||
|      * @param {string} [url] URL to open once the main menu is loaded. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     goToSiteInitialPage(navCtrl?: NavController, page?: string, params?: any, options?: NavOptions): Promise<any> { | ||||
|         return this.openMainMenu(navCtrl, page, params, options); | ||||
|     goToSiteInitialPage(navCtrl?: NavController, page?: string, params?: any, options?: NavOptions, url?: string): Promise<any> { | ||||
|         return this.openMainMenu(navCtrl, page, params, options, url); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -457,33 +495,10 @@ export class CoreLoginHelperProvider { | ||||
|      * Function called when a page starts loading in any InAppBrowser window. | ||||
|      * | ||||
|      * @param {string} url Loaded url. | ||||
|      * @deprecated | ||||
|      */ | ||||
|     inAppBrowserLoadStart(url: string): void { | ||||
|         // URLs with a custom scheme can be prefixed with "http://" or "https://", we need to remove this.
 | ||||
|         url = url.replace(/^https?:\/\//, ''); | ||||
| 
 | ||||
|         if (this.appLaunchedByURL(url)) { | ||||
|             // Close the browser if it's a valid SSO URL.
 | ||||
|             this.utils.closeInAppBrowser(false); | ||||
|         } else if (this.platform.is('android')) { | ||||
|             // Check if the URL has a custom URL scheme. In Android they need to be opened manually.
 | ||||
|             const urlScheme = this.urlUtils.getUrlProtocol(url); | ||||
|             if (urlScheme && urlScheme !== 'file' && urlScheme !== 'cdvfile') { | ||||
|                 // Open in browser should launch the right app if found and do nothing if not found.
 | ||||
|                 this.utils.openInBrowser(url); | ||||
| 
 | ||||
|                 // At this point the InAppBrowser is showing a "Webpage not available" error message.
 | ||||
|                 // Try to navigate to last loaded URL so this error message isn't found.
 | ||||
|                 if (this.lastInAppUrl) { | ||||
|                     this.utils.openInApp(this.lastInAppUrl); | ||||
|                 } else { | ||||
|                     // No last URL loaded, close the InAppBrowser.
 | ||||
|                     this.utils.closeInAppBrowser(false); | ||||
|                 } | ||||
|             } else { | ||||
|                 this.lastInAppUrl = url; | ||||
|             } | ||||
|         } | ||||
|         // This function is deprecated.
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -620,9 +635,10 @@ export class CoreLoginHelperProvider { | ||||
|      * @param {string} page Name of the page to load. | ||||
|      * @param {any} params Params to pass to the page. | ||||
|      * @param {NavOptions} [options] Navigation options. | ||||
|      * @param {string} [url] URL to open once the main menu is loaded. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     protected openMainMenu(navCtrl: NavController, page: string, params: any, options?: NavOptions): Promise<any> { | ||||
|     protected openMainMenu(navCtrl: NavController, page: string, params: any, options?: NavOptions, url?: string): Promise<any> { | ||||
|         navCtrl = navCtrl || this.appProvider.getRootNavController(); | ||||
| 
 | ||||
|         // Due to DeepLinker, we need to remove the path from the URL before going to main menu.
 | ||||
| @ -636,7 +652,7 @@ export class CoreLoginHelperProvider { | ||||
|             }); | ||||
|         } else { | ||||
|             // Open the main menu.
 | ||||
|             return navCtrl.setRoot('CoreMainMenuPage', { redirectPage: page, redirectParams: params }, options); | ||||
|             return navCtrl.setRoot('CoreMainMenuPage', { redirectPage: page, redirectParams: params, urlToOpen: url }, options); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -19,6 +19,8 @@ import { CoreEventsProvider } from '@providers/events'; | ||||
| import { CoreIonTabsComponent } from '@components/ion-tabs/ion-tabs'; | ||||
| import { CoreMainMenuProvider } from '../../providers/mainmenu'; | ||||
| import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../providers/delegate'; | ||||
| import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; | ||||
| import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the main menu of the app. | ||||
| @ -40,12 +42,14 @@ export class CoreMainMenuPage implements OnDestroy { | ||||
|     protected subscription; | ||||
|     protected redirectObs: any; | ||||
|     protected pendingRedirect: any; | ||||
|     protected urlToOpen: string; | ||||
| 
 | ||||
|     @ViewChild('mainTabs') mainTabs: CoreIonTabsComponent; | ||||
| 
 | ||||
|     constructor(private menuDelegate: CoreMainMenuDelegate, private sitesProvider: CoreSitesProvider, navParams: NavParams, | ||||
|             private navCtrl: NavController, private eventsProvider: CoreEventsProvider, private cdr: ChangeDetectorRef, | ||||
|             private mainMenuProvider: CoreMainMenuProvider) { | ||||
|             private mainMenuProvider: CoreMainMenuProvider, private linksDelegate: CoreContentLinksDelegate, | ||||
|             private linksHelper: CoreContentLinksHelperProvider) { | ||||
| 
 | ||||
|         // Check if the menu was loaded with a redirect.
 | ||||
|         const redirectPage = navParams.get('redirectPage'); | ||||
| @ -55,6 +59,8 @@ export class CoreMainMenuPage implements OnDestroy { | ||||
|                 redirectParams: navParams.get('redirectParams') | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         this.urlToOpen = navParams.get('urlToOpen'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -155,6 +161,17 @@ export class CoreMainMenuPage implements OnDestroy { | ||||
|             }); | ||||
| 
 | ||||
|             this.loaded = this.menuDelegate.areHandlersLoaded(); | ||||
|         }); | ||||
| 
 | ||||
|         if (this.urlToOpen) { | ||||
|             // There's a content link to open.
 | ||||
|             this.linksDelegate.getActionsFor(this.urlToOpen, undefined).then((actions) => { | ||||
|                 const action = this.linksHelper.getFirstValidAction(actions); | ||||
|                 if (action && action.sites.length) { | ||||
|                     // Action should only have 1 site because we're filtering by username.
 | ||||
|                     action.action(action.sites[0]); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -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); | ||||
|                             } | ||||
|  | ||||
| @ -565,9 +565,14 @@ export class CoreSitesProvider { | ||||
|      * @param {string} siteUrl The site url. | ||||
|      * @param {string} token User's token. | ||||
|      * @param {string} [privateToken=''] User's private token. | ||||
|      * @return {Promise<any>} A promise resolved when the site is added and the user is authenticated. | ||||
|      * @param {boolean} [login=true] Whether to login the user in the site. Defaults to true. | ||||
|      * @return {Promise<string>} A promise resolved with siteId when the site is added and the user is authenticated. | ||||
|      */ | ||||
|     newSite(siteUrl: string, token: string, privateToken: string = ''): Promise<any> { | ||||
|     newSite(siteUrl: string, token: string, privateToken: string = '', login: boolean = true): Promise<string> { | ||||
|         if (typeof login != 'boolean') { | ||||
|             login = true; | ||||
|         } | ||||
| 
 | ||||
|         // Create a "candidate" site to fetch the site info.
 | ||||
|         const candidateSite = this.sitesFactory.makeSite(undefined, siteUrl, token, undefined, privateToken); | ||||
| 
 | ||||
| @ -585,13 +590,18 @@ export class CoreSitesProvider { | ||||
|                     // Try to get the site config.
 | ||||
|                     return this.getSiteConfig(candidateSite).then((config) => { | ||||
|                         candidateSite.setConfig(config); | ||||
| 
 | ||||
|                         // Add site to sites list.
 | ||||
|                         this.addSite(siteId, siteUrl, token, info, privateToken, config); | ||||
|                         this.sites[siteId] = candidateSite; | ||||
| 
 | ||||
|                         if (login) { | ||||
|                             // Turn candidate site into current site.
 | ||||
|                             this.currentSite = candidateSite; | ||||
|                         this.sites[siteId] = candidateSite; | ||||
|                             // Store session.
 | ||||
|                             this.login(siteId); | ||||
|                         } | ||||
| 
 | ||||
|                         this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, info, siteId); | ||||
| 
 | ||||
|                         return siteId; | ||||
| @ -1477,4 +1487,38 @@ export class CoreSitesProvider { | ||||
|             delete this.siteSchemasMigration[site.id]; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a URL is the root URL of any of the stored sites. | ||||
|      * | ||||
|      * @param {string} url URL to check. | ||||
|      * @param {string} [username] Username to check. | ||||
|      * @return {Promise<{site: CoreSite, siteIds: string[]}>} Promise resolved with site to use and the list of sites that have | ||||
|      *                                   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, siteIds: string[]}> { | ||||
|         // Check if the site is stored.
 | ||||
|         return this.getSiteIdsFromUrl(url, true, username).then((siteIds) => { | ||||
|             const result = { | ||||
|                 siteIds: siteIds, | ||||
|                 site: undefined | ||||
|             }; | ||||
| 
 | ||||
|             if (siteIds.length > 0) { | ||||
|                 // If more than one site is returned it usually means there are different users stored. Use any of them.
 | ||||
|                 return this.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; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										502
									
								
								src/providers/urlschemes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										502
									
								
								src/providers/urlschemes.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,502 @@ | ||||
| // (C) Copyright 2015 Martin Dougiamas
 | ||||
| //
 | ||||
| // 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 { Injectable } from '@angular/core'; | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| import { CoreAppProvider } from './app'; | ||||
| import { CoreInitDelegate } from './init'; | ||||
| import { CoreLoggerProvider } from './logger'; | ||||
| import { CoreSitesProvider } from './sites'; | ||||
| import { CoreDomUtilsProvider } from './utils/dom'; | ||||
| import { CoreTextUtilsProvider } from './utils/text'; | ||||
| import { CoreUrlUtilsProvider } from './utils/url'; | ||||
| import { CoreUtilsProvider } from './utils/utils'; | ||||
| import { CoreLoginHelperProvider } from '@core/login/providers/helper'; | ||||
| import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; | ||||
| import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; | ||||
| import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins'; | ||||
| import { CoreConfigConstants } from '../configconstants'; | ||||
| import { CoreConstants } from '@core/constants'; | ||||
| 
 | ||||
| /** | ||||
|  * All params that can be in a custom URL scheme. | ||||
|  */ | ||||
| export interface CoreCustomURLSchemesParams { | ||||
|     /** | ||||
|      * The site's URL. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     siteUrl: string; | ||||
| 
 | ||||
|     /** | ||||
|      * User's token. If set, user will be authenticated. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     token?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * User's private token. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     privateToken?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Username. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     username?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * URL to open once authenticated. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     redirect?: any; | ||||
| 
 | ||||
|     /** | ||||
|      * Name of the page to go once authenticated. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     pageName?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Params to pass to the page. | ||||
|      * @type {string} | ||||
|      */ | ||||
|     pageParams?: any; | ||||
| } | ||||
| 
 | ||||
| /* | ||||
|  * Provider to handle custom URL schemes. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class CoreCustomURLSchemesProvider { | ||||
|     protected logger; | ||||
| 
 | ||||
|     constructor(logger: CoreLoggerProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, | ||||
|             private loginHelper: CoreLoginHelperProvider, private linksHelper: CoreContentLinksHelperProvider, | ||||
|             private initDelegate: CoreInitDelegate, private domUtils: CoreDomUtilsProvider, private urlUtils: CoreUrlUtilsProvider, | ||||
|             private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, | ||||
|             private linksDelegate: CoreContentLinksDelegate, private translate: TranslateService, | ||||
|             private sitePluginsProvider: CoreSitePluginsProvider) { | ||||
|         this.logger = logger.getInstance('CoreCustomURLSchemesProvider'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Handle an URL received by custom URL scheme. | ||||
|      * | ||||
|      * @param {string} url URL to treat. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     handleCustomURL(url: string): Promise<any> { | ||||
|         if (!this.isCustomURL(url)) { | ||||
|             return Promise.reject(null); | ||||
|         } | ||||
| 
 | ||||
|         let modal, | ||||
|             isSSOToken = false, | ||||
|             data: CoreCustomURLSchemesParams; | ||||
| 
 | ||||
|         // Wait for app to be ready.
 | ||||
|         return this.initDelegate.ready().then(() => { | ||||
|             url = this.textUtils.decodeURIComponent(url); | ||||
| 
 | ||||
|             // Some platforms like Windows add a slash at the end. Remove it.
 | ||||
|             // Some sites add a # at the end of the URL. If it's there, remove it.
 | ||||
|             url = url.replace(/\/?#?\/?$/, ''); | ||||
| 
 | ||||
|             modal = this.domUtils.showModalLoading(); | ||||
| 
 | ||||
|             // Get the data from the URL.
 | ||||
|             if (this.isCustomURLToken(url)) { | ||||
|                 isSSOToken = true; | ||||
| 
 | ||||
|                 return this.getCustomURLTokenData(url); | ||||
|             } else if (this.isCustomURLLink(url)) { | ||||
|                 return this.getCustomURLLinkData(url); | ||||
|             } else { | ||||
|                 return this.getCustomURLData(url); | ||||
|             } | ||||
|         }).then((result) => { | ||||
|             data = result; | ||||
| 
 | ||||
|             if (data.redirect && data.redirect.match(/^https?:\/\//) && data.redirect.indexOf(data.siteUrl) == -1) { | ||||
|                 // Redirect URL must belong to the same site. Reject.
 | ||||
|                 return Promise.reject(this.translate.instant('core.contentlinks.errorredirectothersite')); | ||||
|             } | ||||
| 
 | ||||
|             // First of all, authenticate the user if needed.
 | ||||
|             const currentSite = this.sitesProvider.getCurrentSite(); | ||||
| 
 | ||||
|             if (data.token) { | ||||
|                 if (!currentSite || currentSite.getToken() != data.token) { | ||||
|                     // Token belongs to a different site, create it. It doesn't matter if it already exists.
 | ||||
|                     let promise; | ||||
| 
 | ||||
|                     if (!data.siteUrl.match(/^https?:\/\//)) { | ||||
|                         // URL doesn't have a protocol and it's required to be able to create the site. Check which one to use.
 | ||||
|                         promise = this.sitesProvider.checkSite(data.siteUrl).then((result) => { | ||||
|                             data.siteUrl = result.siteUrl; | ||||
|                         }); | ||||
|                     } else { | ||||
|                         promise = Promise.resolve(); | ||||
|                     } | ||||
| 
 | ||||
|                     return promise.then(() => { | ||||
|                         return this.sitesProvider.newSite(data.siteUrl, data.token, data.privateToken, isSSOToken); | ||||
|                     }); | ||||
|                 } else { | ||||
|                     // Token belongs to current site, no need to create it.
 | ||||
|                     return this.sitesProvider.getCurrentSiteId(); | ||||
|                 } | ||||
|             } | ||||
|         }).then((siteId) => { | ||||
|             if (isSSOToken) { | ||||
|                 // Site created and authenticated, open the page to go.
 | ||||
|                 if (data.pageName) { | ||||
|                     // State defined, go to that state instead of site initial page.
 | ||||
|                     this.appProvider.getRootNavController().push(data.pageName, data.pageParams); | ||||
|                 } else { | ||||
|                     this.loginHelper.goToSiteInitialPage(); | ||||
|                 } | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (data.redirect && !data.redirect.match(/^https?:\/\//)) { | ||||
|                 // Redirect is a relative URL. Append the site URL.
 | ||||
|                 data.redirect = this.textUtils.concatenatePaths(data.siteUrl, data.redirect); | ||||
|             } | ||||
| 
 | ||||
|             let promise; | ||||
| 
 | ||||
|             if (siteId) { | ||||
|                 // Site created, we know the site to use.
 | ||||
|                 promise = Promise.resolve([siteId]); | ||||
|             } else { | ||||
|                 // Check if the site is stored.
 | ||||
|                 promise = this.sitesProvider.getSiteIdsFromUrl(data.siteUrl, true, data.username); | ||||
|             } | ||||
| 
 | ||||
|             return promise.then((siteIds) => { | ||||
|                 if (siteIds.length > 1) { | ||||
|                     // More than one site to treat the URL, let the user choose.
 | ||||
|                     this.linksHelper.goToChooseSite(data.redirect || data.siteUrl); | ||||
| 
 | ||||
|                 } else if (siteIds.length == 1) { | ||||
|                     // Only one site, handle the link.
 | ||||
|                     return this.sitesProvider.getSite(siteIds[0]).then((site) => { | ||||
|                         if (!data.redirect) { | ||||
|                             // No redirect, go to the root URL if needed.
 | ||||
| 
 | ||||
|                             return this.linksHelper.handleRootURL(site, false, true); | ||||
|                         } else { | ||||
|                             // Handle the redirect link.
 | ||||
|                             modal.dismiss(); // Dismiss modal so it doesn't collide with confirms.
 | ||||
| 
 | ||||
|                             /* Always use the username from the site in this case. If the link has a username and a token, | ||||
|                                this will make sure that the link is opened with the user the token belongs to. */ | ||||
|                             const username = site.getInfo().username || data.username; | ||||
| 
 | ||||
|                             return this.linksHelper.handleLink(data.redirect, username).then((treated) => { | ||||
|                                 if (!treated) { | ||||
|                                     this.domUtils.showErrorModal('core.contentlinks.errornoactions', true); | ||||
|                                 } | ||||
|                             }); | ||||
|                         } | ||||
|                     }); | ||||
| 
 | ||||
|                 } else { | ||||
|                     // Site not stored. Try to add the site.
 | ||||
|                     return this.sitesProvider.checkSite(data.siteUrl).then((result) => { | ||||
|                         // Site exists. We'll allow to add it.
 | ||||
|                         const ssoNeeded = this.loginHelper.isSSOLoginNeeded(result.code), | ||||
|                             pageName = 'CoreLoginCredentialsPage', | ||||
|                             pageParams = { | ||||
|                                 siteUrl: result.siteUrl, | ||||
|                                 username: data.username, | ||||
|                                 urlToOpen: data.redirect, | ||||
|                                 siteConfig: result.config | ||||
|                             }; | ||||
|                         let promise, | ||||
|                             hasSitePluginsLoaded = false; | ||||
| 
 | ||||
|                         modal.dismiss(); // Dismiss modal so it doesn't collide with confirms.
 | ||||
| 
 | ||||
|                         if (!this.sitesProvider.isLoggedIn()) { | ||||
|                             // Not logged in, no need to confirm. If SSO the confirm will be shown later.
 | ||||
|                             promise = Promise.resolve(); | ||||
|                         } else { | ||||
|                             // Ask the user before changing site.
 | ||||
|                             const confirmMsg = this.translate.instant('core.contentlinks.confirmurlothersite'); | ||||
|                             promise = this.domUtils.showConfirm(confirmMsg).then(() => { | ||||
|                                 if (!ssoNeeded) { | ||||
|                                     hasSitePluginsLoaded = this.sitePluginsProvider.hasSitePluginsLoaded; | ||||
|                                     if (hasSitePluginsLoaded) { | ||||
|                                         // Store the redirect since logout will restart the app.
 | ||||
|                                         this.appProvider.storeRedirect(CoreConstants.NO_SITE_ID, pageName, pageParams); | ||||
|                                     } | ||||
| 
 | ||||
|                                     return this.sitesProvider.logout().catch(() => { | ||||
|                                         // Ignore errors (shouldn't happen).
 | ||||
|                                     }); | ||||
|                                 } | ||||
|                             }); | ||||
|                         } | ||||
| 
 | ||||
|                         return promise.then(() => { | ||||
|                             if (ssoNeeded) { | ||||
|                                 this.loginHelper.confirmAndOpenBrowserForSSOLogin( | ||||
|                                     result.siteUrl, result.code, result.service, result.config && result.config.launchurl); | ||||
|                             } else if (!hasSitePluginsLoaded) { | ||||
|                                 return this.loginHelper.goToNoSitePage(undefined, pageName, pageParams); | ||||
|                             } | ||||
|                         }); | ||||
| 
 | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|         }).catch((error) => { | ||||
|             if (error && isSSOToken) { | ||||
|                 // An error occurred, display the error and logout the user.
 | ||||
|                 this.loginHelper.treatUserTokenError(data.siteUrl, error); | ||||
|                 this.sitesProvider.logout(); | ||||
|             } else { | ||||
|                 this.domUtils.showErrorModalDefault(error, this.translate.instant('core.login.invalidsite')); | ||||
|             } | ||||
|         }).finally(() => { | ||||
|             modal.dismiss(); | ||||
| 
 | ||||
|             if (isSSOToken) { | ||||
|                 this.appProvider.finishSSOAuthentication(); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the data from a custom URL scheme. The structure of the URL is: | ||||
|      * moodlemobile://username@domain.com?token=TOKEN&privatetoken=PRIVATETOKEN&redirect=http://domain.com/course/view.php?id=2
 | ||||
|      * | ||||
|      * @param {string} url URL to treat. | ||||
|      * @return {Promise<CoreCustomURLSchemesParams>} Promise resolved with the data. | ||||
|      */ | ||||
|     protected getCustomURLData(url: string): Promise<CoreCustomURLSchemesParams> { | ||||
|         const urlScheme = CoreConfigConstants.customurlscheme + '://'; | ||||
|         if (url.indexOf(urlScheme) == -1) { | ||||
|             return Promise.reject(null); | ||||
|         } | ||||
| 
 | ||||
|         // App opened using custom URL scheme.
 | ||||
|         this.logger.debug('Treating custom URL scheme: ' + url); | ||||
| 
 | ||||
|         // Delete the sso scheme from the URL.
 | ||||
|         url = url.replace(urlScheme, ''); | ||||
| 
 | ||||
|         // Detect if there's a user specified.
 | ||||
|         const username = this.urlUtils.getUsernameFromUrl(url); | ||||
|         if (username) { | ||||
|             url = url.replace(username + '@', ''); // Remove the username from the URL.
 | ||||
|         } | ||||
| 
 | ||||
|         // Get the params of the URL.
 | ||||
|         const params = this.urlUtils.extractUrlParams(url); | ||||
| 
 | ||||
|         // Remove the params to get the site URL.
 | ||||
|         if (url.indexOf('?') != -1) { | ||||
|             url = url.substr(0, url.indexOf('?')); | ||||
|         } | ||||
| 
 | ||||
|         let promise; | ||||
| 
 | ||||
|         if (!url.match(/https?:\/\//)) { | ||||
|             // Url doesn't have a protocol. Check if the site is stored in the app to be able to determine the protocol.
 | ||||
|             promise = this.sitesProvider.getSiteIdsFromUrl(url, true, username).then((siteIds) => { | ||||
|                 if (siteIds.length) { | ||||
|                     // There is at least 1 site with this URL. Use it to know the full URL.
 | ||||
|                     return this.sitesProvider.getSite(siteIds[0]).then((site) => { | ||||
|                         return site.getURL(); | ||||
|                     }); | ||||
|                 } else { | ||||
|                     // No site stored with this URL, just use the URL as it is.
 | ||||
|                     return url; | ||||
|                 } | ||||
|             }); | ||||
|         } else { | ||||
|             promise = Promise.resolve(url); | ||||
|         } | ||||
| 
 | ||||
|         return promise.then((url) => { | ||||
|             return { | ||||
|                 siteUrl: url, | ||||
|                 username: username, | ||||
|                 token: params.token, | ||||
|                 privateToken: params.privateToken, | ||||
|                 redirect: params.redirect | ||||
|             }; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the data from a "link" custom URL scheme. This kind of URL is deprecated. | ||||
|      * | ||||
|      * @param {string} url URL to treat. | ||||
|      * @return {Promise<CoreCustomURLSchemesParams>} Promise resolved with the data. | ||||
|      */ | ||||
|     protected getCustomURLLinkData(url: string): Promise<CoreCustomURLSchemesParams> { | ||||
|         const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link='; | ||||
|         if (url.indexOf(contentLinksScheme) == -1) { | ||||
|             return Promise.reject(null); | ||||
|         } | ||||
| 
 | ||||
|         // App opened using custom URL scheme.
 | ||||
|         this.logger.debug('Treating custom URL scheme with link param: ' + url); | ||||
| 
 | ||||
|         // Delete the sso scheme from the URL.
 | ||||
|         url = url.replace(contentLinksScheme, ''); | ||||
| 
 | ||||
|         // Detect if there's a user specified.
 | ||||
|         const username = this.urlUtils.getUsernameFromUrl(url); | ||||
|         if (username) { | ||||
|             url = url.replace(username + '@', ''); // Remove the username from the URL.
 | ||||
|         } | ||||
| 
 | ||||
|         // First of all, check if it's the root URL of a site.
 | ||||
|         return this.sitesProvider.isStoredRootURL(url, username).then((data): any => { | ||||
| 
 | ||||
|             if (data.site) { | ||||
|                 // Root URL.
 | ||||
|                 return { | ||||
|                     siteUrl: data.site.getURL(), | ||||
|                     username: username | ||||
|                 }; | ||||
| 
 | ||||
|             } else if (data.siteIds.length > 0) { | ||||
|                 // Not the root URL, but at least 1 site supports the URL. Get the site URL from the list of sites.
 | ||||
|                 return this.sitesProvider.getSite(data.siteIds[0]).then((site) => { | ||||
|                     return { | ||||
|                         siteUrl: site.getURL(), | ||||
|                         username: username, | ||||
|                         redirect: url | ||||
|                     }; | ||||
|                 }); | ||||
| 
 | ||||
|             } else { | ||||
|                 // Get the site URL.
 | ||||
|                 let siteUrl = this.linksDelegate.getSiteUrl(url), | ||||
|                     redirect = url; | ||||
| 
 | ||||
|                 if (!siteUrl) { | ||||
|                     // Site URL not found, use the original URL since it could be the root URL of the site.
 | ||||
|                     siteUrl = url; | ||||
|                     redirect = undefined; | ||||
|                 } | ||||
| 
 | ||||
|                 return { | ||||
|                     siteUrl: siteUrl, | ||||
|                     username: username, | ||||
|                     redirect: redirect | ||||
|                 }; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the data from a "token" custom URL scheme. This kind of URL is deprecated. | ||||
|      * | ||||
|      * @param {string} url URL to treat. | ||||
|      * @return {Promise<CoreCustomURLSchemesParams>} Promise resolved with the data. | ||||
|      */ | ||||
|     protected getCustomURLTokenData(url: string): Promise<CoreCustomURLSchemesParams> { | ||||
|         const ssoScheme = CoreConfigConstants.customurlscheme + '://token='; | ||||
|         if (url.indexOf(ssoScheme) == -1) { | ||||
|             return Promise.reject(null); | ||||
|         } | ||||
| 
 | ||||
|         if (this.appProvider.isSSOAuthenticationOngoing()) { | ||||
|             // Authentication ongoing, probably duplicated request.
 | ||||
|             return Promise.reject(null); | ||||
|         } | ||||
| 
 | ||||
|         if (this.appProvider.isDesktop()) { | ||||
|             // In desktop, make sure InAppBrowser is closed.
 | ||||
|             this.utils.closeInAppBrowser(true); | ||||
|         } | ||||
| 
 | ||||
|         // App opened using custom URL scheme. Probably an SSO authentication.
 | ||||
|         this.appProvider.startSSOAuthentication(); | ||||
|         this.logger.debug('App launched by URL with an SSO'); | ||||
| 
 | ||||
|         // Delete the sso scheme from the URL.
 | ||||
|         url = url.replace(ssoScheme, ''); | ||||
| 
 | ||||
|         // Some platforms like Windows add a slash at the end. Remove it.
 | ||||
|         // Some sites add a # at the end of the URL. If it's there, remove it.
 | ||||
|         url = url.replace(/\/?#?\/?$/, ''); | ||||
| 
 | ||||
|         // Decode from base64.
 | ||||
|         try { | ||||
|             url = atob(url); | ||||
|         } catch (err) { | ||||
|             // Error decoding the parameter.
 | ||||
|             this.logger.error('Error decoding parameter received for login SSO'); | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         return this.loginHelper.validateBrowserSSOLogin(url); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check whether a URL is a custom URL scheme. | ||||
|      * | ||||
|      * @param {string} url URL to check. | ||||
|      * @return {boolean} Whether it's a custom URL scheme. | ||||
|      */ | ||||
|     isCustomURL(url: string): boolean { | ||||
|         if (!url) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return url.indexOf(CoreConfigConstants.customurlscheme + '://') != -1; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check whether a URL is a custom URL scheme with the "link" param (deprecated). | ||||
|      * | ||||
|      * @param {string} url URL to check. | ||||
|      * @return {boolean} Whether it's a custom URL scheme. | ||||
|      */ | ||||
|     isCustomURLLink(url: string): boolean { | ||||
|         if (!url) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return url.indexOf(CoreConfigConstants.customurlscheme + '://link=') != -1; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check whether a URL is a custom URL scheme with a "token" param (deprecated). | ||||
|      * | ||||
|      * @param {string} url URL to check. | ||||
|      * @return {boolean} Whether it's a custom URL scheme. | ||||
|      */ | ||||
|     isCustomURLToken(url: string): boolean { | ||||
|         if (!url) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return url.indexOf(CoreConfigConstants.customurlscheme + '://token=') != -1; | ||||
|     } | ||||
| } | ||||
| @ -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. | ||||
|      * | ||||
|  | ||||
| @ -52,12 +52,28 @@ export class CoreUrlUtilsProvider { | ||||
|      */ | ||||
|     extractUrlParams(url: string): any { | ||||
|         const regex = /[?&]+([^=&]+)=?([^&]*)?/gi, | ||||
|             subParamsPlaceholder = '@@@SUBPARAMS@@@', | ||||
|             params: any = {}, | ||||
|             urlAndHash = url.split('#'); | ||||
|             urlAndHash = url.split('#'), | ||||
|             questionMarkSplit = urlAndHash[0].split('?'); | ||||
|         let subParams; | ||||
| 
 | ||||
|         if (questionMarkSplit.length > 2) { | ||||
|             // There is more than one question mark in the URL. This can happen if any of the params is a URL with params.
 | ||||
|             // We only want to treat the first level of params, so we'll remove this second list of params and restore it later.
 | ||||
|             questionMarkSplit.splice(0, 2); | ||||
| 
 | ||||
|             subParams = '?' + questionMarkSplit.join('?'); | ||||
|             urlAndHash[0] = urlAndHash[0].replace(subParams, subParamsPlaceholder); | ||||
|         } | ||||
| 
 | ||||
|         urlAndHash[0].replace(regex, (match: string, key: string, value: string): string => { | ||||
|             params[key] = typeof value != 'undefined' ? value : ''; | ||||
| 
 | ||||
|             if (subParams) { | ||||
|                 params[key] = params[key].replace(subParamsPlaceholder, subParams); | ||||
|             } | ||||
| 
 | ||||
|             return match; | ||||
|         }); | ||||
| 
 | ||||
| @ -229,7 +245,7 @@ export class CoreUrlUtilsProvider { | ||||
|     getUsernameFromUrl(url: string): string { | ||||
|         if (url.indexOf('@') > -1) { | ||||
|             // Get URL without protocol.
 | ||||
|             const withoutProtocol = url.replace(/.*?:\/\//, ''), | ||||
|             const withoutProtocol = url.replace(/^[^?@\/]*:\/\//, ''), | ||||
|                 matches = withoutProtocol.match(/[^@]*/); | ||||
| 
 | ||||
|             // Make sure that @ is at the start of the URL, not in a param at the end.
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user