Merge pull request #2132 from crazyserver/MOBILE-3177

Mobile 3177
main
Juan Leyva 2019-10-24 10:19:20 +02:00 committed by GitHub
commit 122bd8bbe8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 244 additions and 47 deletions

View File

@ -20,26 +20,22 @@ function print_success {
}
function print_error {
tput setaf 1; echo " ERROR: $1"
tput setaf 0
tput setaf 1; echo " ERROR: $1"; tput sgr0
}
function print_ok {
tput setaf 2; echo " OK: $1"
tput setaf 2; echo " OK: $1"; tput sgr0
echo
tput setaf 0
}
function print_message {
tput setaf 3; echo "-------- $1"
tput setaf 3; echo "-------- $1"; tput sgr0
echo
tput setaf 0
}
function print_title {
stepnumber=$(($stepnumber + 1))
echo
tput setaf 5; echo "$stepnumber $1"
tput setaf 5; echo '=================='
tput setaf 0
tput setaf 5; echo "$stepnumber $1"; tput sgr0
tput setaf 5; echo '=================='; tput sgr0
}

View File

@ -1567,7 +1567,7 @@
"core.login.changepassword": "moodle",
"core.login.changepasswordbutton": "local_moodlemobileapp",
"core.login.changepasswordhelp": "local_moodlemobileapp",
"core.login.changepassowrdinstructions": "local_moodlemobileapp",
"core.login.changepasswordinstructions": "local_moodlemobileapp",
"core.login.changepasswordlogoutinstructions": "local_moodlemobileapp",
"core.login.changepasswordreconnectinstructions": "local_moodlemobileapp",
"core.login.checksiteversion": "local_moodlemobileapp",
@ -1925,6 +1925,8 @@
"core.unknown": "local_moodlemobileapp",
"core.unlimited": "moodle",
"core.unzipping": "local_moodlemobileapp",
"core.updaterequired": "local_moodlemobileapp",
"core.updaterequireddesc": "local_moodlemobileapp",
"core.upgraderunning": "error",
"core.user": "moodle",
"core.user.address": "moodle",

View File

@ -1562,10 +1562,10 @@
"core.login.auth_email": "Email-based self-registration",
"core.login.authenticating": "Authenticating",
"core.login.cancel": "Cancel",
"core.login.changepassowrdinstructions": "You cannot change your password in the app. Please click the following button to open the site in a web browser to change your password. Take into account you need to close the browser after changing the password as you will not be redirected to the app.",
"core.login.changepassword": "Change password",
"core.login.changepasswordbutton": "Open the change password page",
"core.login.changepasswordhelp": "If you have problems changing your password, please contact your site administrator. \"Site Administrators\" are the people who manages the Moodle at your school/university/company or learning organisation. If you don't know how to contact them, please contact your teachers/trainers.",
"core.login.changepasswordinstructions": "You cannot change your password in the app. Please click the following button to open the site in a web browser to change your password. Take into account you need to close the browser after changing the password as you will not be redirected to the app.",
"core.login.changepasswordlogoutinstructions": "If you prefer to change site or log out, please click the following button:",
"core.login.changepasswordreconnectinstructions": "Click the following button to reconnect to the site. (Take into account that if you didn't change your password successfully, you would return to the previous screen).",
"core.login.checksiteversion": "Check that your site uses Moodle 3.1 or later.",
@ -1920,6 +1920,8 @@
"core.unknown": "Unknown",
"core.unlimited": "Unlimited",
"core.unzipping": "Unzipping",
"core.updaterequired": "App update required",
"core.updaterequireddesc": "Please update your app to version {{$a}}",
"core.upgraderunning": "Site is being upgraded, please retry later.",
"core.user": "User",
"core.user.address": "Address",

View File

@ -25,8 +25,8 @@ ion-app.app-root {
padding-left: 0 !important;
padding-right: 0 !important;
> .core-loading-content > *,
> .core-loading-content-loading > * {
> .core-loading-content > *:not[padding],
> .core-loading-content-loading > *:not[padding] {
@include safe-area-padding-horizontal(0px, 0px);
}
}

View File

@ -6,7 +6,7 @@
"changepassword": "Change password",
"changepasswordbutton": "Open the change password page",
"changepasswordhelp": "If you have problems changing your password, please contact your site administrator. \"Site Administrators\" are the people who manages the Moodle at your school/university/company or learning organisation. If you don't know how to contact them, please contact your teachers/trainers.",
"changepassowrdinstructions": "You cannot change your password in the app. Please click the following button to open the site in a web browser to change your password. Take into account you need to close the browser after changing the password as you will not be redirected to the app.",
"changepasswordinstructions": "You cannot change your password in the app. Please click the following button to open the site in a web browser to change your password. Take into account you need to close the browser after changing the password as you will not be redirected to the app.",
"changepasswordlogoutinstructions": "If you prefer to change site or log out, please click the following button:",
"changepasswordreconnectinstructions": "Click the following button to reconnect to the site. (Take into account that if you didn't change your password successfully, you would return to the previous screen).",
"confirmdeletesite": "Are you sure you want to delete the site {{sitename}}?",

View File

@ -13,12 +13,15 @@
// limitations under the License.
import { NgModule } from '@angular/core';
import { CoreCronDelegate } from '@providers/cron';
import { CoreLoginHelperProvider } from './providers/helper';
import { CoreLoginCronHandler } from './providers/cron-handler';
import { CoreLoginSitesPageModule } from './pages/sites/sites.module';
// List of providers.
export const CORE_LOGIN_PROVIDERS = [
CoreLoginHelperProvider
CoreLoginHelperProvider,
CoreLoginCronHandler
];
@NgModule({
@ -29,4 +32,9 @@ export const CORE_LOGIN_PROVIDERS = [
],
providers: CORE_LOGIN_PROVIDERS
})
export class CoreLoginModule {}
export class CoreLoginModule {
constructor(cronDelegate: CoreCronDelegate, cronHandler: CoreLoginCronHandler) {
// Register handlers.
cronDelegate.register(cronHandler);
}
}

View File

@ -12,7 +12,7 @@
<ion-list>
<ion-item text-wrap *ngIf="!changingPassword">
<h2>{{ 'core.login.forcepasswordchangenotice' | translate }}</h2>
<p padding-vertical>{{ 'core.login.changepassowrdinstructions' | translate }}</p>
<p padding-vertical>{{ 'core.login.changepasswordinstructions' | translate }}</p>
<button text-wrap ion-button block (click)="openChangePasswordPage()">{{ 'core.login.changepasswordbutton' | translate }}</button>
</ion-item>
<ion-item text-wrap *ngIf="changingPassword">

View File

@ -85,15 +85,22 @@ export class CoreLoginInitPage {
*/
protected loadPage(): Promise<any> {
if (this.sitesProvider.isLoggedIn()) {
if (!this.loginHelper.isSiteLoggedOut()) {
// User is logged in, go to site initial page.
return this.loginHelper.goToSiteInitialPage();
} else {
// The site is marked as logged out. Logout and try again.
if (this.loginHelper.isSiteLoggedOut()) {
return this.sitesProvider.logout().then(() => {
return this.loadPage();
});
}
return this.sitesProvider.getCurrentSite().getPublicConfig().catch(() => {
return {};
}).then((config) => {
return this.sitesProvider.checkRequiredMinimumVersion(config).then(() => {
// User is logged in, go to site initial page.
return this.loginHelper.goToSiteInitialPage();
}).catch(() => {
return this.loadPage();
});
});
}
return this.navCtrl.setRoot('CoreLoginSitesPage');

View File

@ -82,16 +82,20 @@ export class CoreLoginReconnectPage {
this.siteUrl = site.infos.siteurl;
this.siteName = site.getSiteName();
// Check logoURL if user avatar is not set.
if (this.site.avatar.startsWith(site.infos.siteurl + '/theme/image.php')) {
this.site.avatar = false;
return site.getPublicConfig().then((config) => {
return this.sitesProvider.checkRequiredMinimumVersion(config).then(() => {
// Check logoURL if user avatar is not set.
if (this.site.avatar.startsWith(site.infos.siteurl + '/theme/image.php')) {
this.site.avatar = false;
return site.getPublicConfig().then((config) => {
this.logoUrl = config.logourl || config.compactlogourl;
this.logoUrl = config.logourl || config.compactlogourl;
}
}).catch(() => {
// Ignore errors.
this.cancel();
});
}
}).catch(() => {
// Ignore errors.
});
}).catch(() => {
// Shouldn't happen. Just leave the view.
this.cancel();

View File

@ -112,18 +112,21 @@ export class CoreLoginSitePage {
} else {
// Not a demo site.
this.sitesProvider.checkSite(url).then((result) => {
return this.sitesProvider.checkRequiredMinimumVersion(result.config).then(() => {
if (result.warning) {
this.domUtils.showErrorModal(result.warning, true, 4000);
}
if (result.warning) {
this.domUtils.showErrorModal(result.warning, true, 4000);
}
if (this.loginHelper.isSSOLoginNeeded(result.code)) {
// SSO. User needs to authenticate in a browser.
this.loginHelper.confirmAndOpenBrowserForSSOLogin(
result.siteUrl, result.code, result.service, result.config && result.config.launchurl);
} else {
this.navCtrl.push('CoreLoginCredentialsPage', { siteUrl: result.siteUrl, siteConfig: result.config });
}
if (this.loginHelper.isSSOLoginNeeded(result.code)) {
// SSO. User needs to authenticate in a browser.
this.loginHelper.confirmAndOpenBrowserForSSOLogin(
result.siteUrl, result.code, result.service, result.config && result.config.launchurl);
} else {
this.navCtrl.push('CoreLoginCredentialsPage', { siteUrl: result.siteUrl, siteConfig: result.config });
}
}).catch(() => {
// Ignore errors.
});
}, (error) => {
this.showLoginIssue(url, error);
}).finally(() => {

View File

@ -0,0 +1,64 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreCronHandler } from '@providers/cron';
import { CoreSitesProvider } from '@providers/sites';
/**
* Cron handler to log out sites when does not meet the app requirements.
*/
@Injectable()
export class CoreLoginCronHandler implements CoreCronHandler {
name = 'CoreLoginCronHandler';
constructor(private sitesProvider: CoreSitesProvider) {}
/**
* Execute the process.
* Receives the ID of the site affected, undefined for all sites.
*
* @param siteId ID of the site affected, undefined for all sites.
* @return Promise resolved when done, rejected if failure.
*/
execute(siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (!siteId) {
return Promise.resolve();
}
// Check logged in site minimun required version.
// Do not check twice in the same 10 minutes.
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getPublicConfig().catch(() => {
return {};
}).then((config) => {
this.sitesProvider.checkRequiredMinimumVersion(config).catch(() => {
// Ignore errors.
});
});
});
}
/**
* Check whether it's a synchronization process or not. True if not defined.
*
* @return Whether it's a synchronization process or not.
*/
isSync(): boolean {
// Defined to true to be checked on sync site.
return true;
}
}

View File

@ -171,9 +171,9 @@ export class CoreSettingsHelper {
});
}));
const syncPromise = Promise.all(promises);
let syncPromise = Promise.all(promises);
this.syncPromises[siteId] = syncPromise;
syncPromise.finally(() => {
syncPromise = syncPromise.finally(() => {
delete this.syncPromises[siteId];
});

View File

@ -273,6 +273,8 @@
"unlimited": "Unlimited",
"unzipping": "Unzipping",
"upgraderunning": "Site is being upgraded, please retry later.",
"updaterequired": "App update required",
"updaterequireddesc": "Please update your app to version {{$a}}",
"user": "User",
"userdeleted": "This user account has been deleted",
"userdetails": "User details",

View File

@ -170,6 +170,15 @@ export class CoreAppProvider {
return this.isDesktop() && process.arch == 'x64';
}
/**
* Checks if the app is running in an Android mobile or tablet device.
*
* @return Whether the app is running in an Android mobile or tablet device.
*/
isAndroid(): boolean {
return this.platform.is('android');
}
/**
* Checks if the app is running in a desktop environment (not browser).
*
@ -181,6 +190,15 @@ export class CoreAppProvider {
return !!(process && process.versions && typeof process.versions.electron != 'undefined');
}
/**
* Checks if the app is running in an iOS mobile or tablet device.
*
* @return Whether the app is running in an iOS mobile or tablet device.
*/
isIOS(): boolean {
return this.platform.is('ios');
}
/**
* Check if the keyboard is visible.
*

View File

@ -19,6 +19,7 @@ import { CoreAppProvider } from './app';
import { CoreEventsProvider } from './events';
import { CoreLoggerProvider } from './logger';
import { CoreSitesFactoryProvider } from './sites-factory';
import { CoreDomUtilsProvider } from './utils/dom';
import { CoreTextUtilsProvider } from './utils/text';
import { CoreUrlUtilsProvider } from './utils/url';
import { CoreUtilsProvider } from './utils/utils';
@ -311,7 +312,8 @@ export class CoreSitesProvider {
constructor(logger: CoreLoggerProvider, private http: HttpClient, private sitesFactory: CoreSitesFactoryProvider,
private appProvider: CoreAppProvider, private translate: TranslateService, private urlUtils: CoreUrlUtilsProvider,
private eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider,
private utils: CoreUtilsProvider, private injector: Injector, private wsProvider: CoreWSProvider) {
private utils: CoreUtilsProvider, private injector: Injector, private wsProvider: CoreWSProvider,
protected domUtils: CoreDomUtilsProvider) {
this.logger = logger.getInstance('CoreSitesProvider');
this.appDB = appProvider.getDB();
@ -862,6 +864,87 @@ export class CoreSitesProvider {
return this.appDB.insertRecord(this.SITES_TABLE, entry);
}
/**
* Check the required minimum version of the app for a site and shows a download dialog.
*
* @param config Config object of the site.
* @param siteId ID of the site to check. Current site id will be used otherwise.
* @return Resolved with if meets the requirements, rejected otherwise.
*/
checkRequiredMinimumVersion(config: any, siteId?: string): Promise<void> {
if (config && config.tool_mobile_minimumversion) {
const requiredVersion = this.convertVersionName(config.tool_mobile_minimumversion),
appVersion = this.convertVersionName(CoreConfigConstants.versionname);
if (requiredVersion > appVersion) {
let downloadUrl = '';
if (this.appProvider.isAndroid() && config.tool_mobile_androidappid) {
downloadUrl = 'market://details?id=' + config.tool_mobile_androidappid;
} else if (this.appProvider.isIOS() && config.tool_mobile_iosappid) {
downloadUrl = 'itms-apps://itunes.apple.com/app/id' + config.tool_mobile_iosappid;
} else if (config.tool_mobile_setuplink) {
downloadUrl = config.tool_mobile_setuplink;
} else if (this.appProvider.isMobile()) {
downloadUrl = 'https://download.moodle.org/mobile/';
} else {
downloadUrl = 'https://download.moodle.org/desktop/';
}
siteId = siteId || this.getCurrentSiteId();
// Do not block interface.
this.domUtils.showConfirm(
this.translate.instant('core.updaterequireddesc', { $a: config.tool_mobile_minimumversion }),
this.translate.instant('core.updaterequired'),
this.translate.instant('core.download'),
this.translate.instant(siteId ? 'core.mainmenu.logout' : 'core.cancel')).then(() => {
this.utils.openInBrowser(downloadUrl);
}).catch(() => {
// Do nothing.
});
if (siteId) {
// Logout if it's the currentSite.
const promise = siteId == this.getCurrentSiteId() ? this.logout() : Promise.resolve();
return promise.then(() => {
// Always expire the token.
return this.setSiteLoggedOut(siteId, true);
}).then(() => {
return Promise.reject(null);
});
}
return Promise.reject(null);
}
}
return Promise.resolve();
}
/**
* Convert version name to numbers.
*
* @param name Version name (dot separated).
* @return Version translated to a comparable number.
*/
protected convertVersionName(name: string): number {
let version = 0;
const parts = name.split('.', 3);
parts.forEach((num) => {
version = (version * 100) + Number(num);
});
if (parts.length < 3) {
version = version * Math.pow(100, 3 - parts.length);
}
return version;
}
/**
* Login a user to a site from the list of sites.
*
@ -896,12 +979,20 @@ export class CoreSitesProvider {
return false;
}, () => {
this.login(siteId);
return site.getPublicConfig().catch(() => {
return {};
}).then((config) => {
return this.checkRequiredMinimumVersion(config).then(() => {
this.login(siteId);
// Update site info. We don't block the UI.
this.updateSiteInfo(siteId);
// Update site info. We don't block the UI.
this.updateSiteInfo(siteId);
return true;
return true;
}).catch(() => {
return false;
});
});
});
});
}