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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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…
Reference in New Issue