Skip to content

Commit 988a5b6

Browse files
authored
Merge pull request #4274 from crazyserver/MOBILE-4680
Mobile 4680
2 parents 6af9840 + 6c132bd commit 988a5b6

File tree

15 files changed

+231
-99
lines changed

15 files changed

+231
-99
lines changed

scripts/langindex.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,6 +2168,7 @@
21682168
"core.login.missingfirstname": "moodle",
21692169
"core.login.missinglastname": "moodle",
21702170
"core.login.mobileservicesnotenabled": "local_moodlemobileapp",
2171+
"core.login.morewaystologin": "local_moodlemobileapp",
21712172
"core.login.mustconfirm": "moodle",
21722173
"core.login.newaccount": "moodle",
21732174
"core.login.notloggedin": "local_moodlemobileapp",

src/core/classes/sites/site.ts

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ export class CoreSite extends CoreAuthenticatedSite {
195195
* Check if the user authenticated in the site using an OAuth method.
196196
*
197197
* @returns Whether the user authenticated in the site using an OAuth method.
198+
* @deprecated since 5.0. Use getOAuthId instead.
198199
*/
199200
isOAuth(): boolean {
200201
return this.oauthId != null && this.oauthId !== undefined;
@@ -268,7 +269,6 @@ export class CoreSite extends CoreAuthenticatedSite {
268269
*
269270
* @param component Component name.
270271
* @param componentId Component id.
271-
* @returns Promise resolved when the entries are deleted.
272272
*/
273273
async deleteComponentFromCache(component: string, componentId?: number): Promise<void> {
274274
if (!component) {
@@ -284,7 +284,7 @@ export class CoreSite extends CoreAuthenticatedSite {
284284
await this.cacheTable.delete(params);
285285
}
286286

287-
/*
287+
/**
288288
* Uploads a file using Cordova File API.
289289
*
290290
* @param filePath File path.
@@ -366,13 +366,17 @@ export class CoreSite extends CoreAuthenticatedSite {
366366
* @param url The url to be fixed.
367367
* @returns Promise resolved with the fixed URL.
368368
*/
369-
checkAndFixPluginfileURL(url: string): Promise<string> {
370-
return this.checkTokenPluginFile(url).then(() => this.fixPluginfileURL(url));
369+
async checkAndFixPluginfileURL(url: string): Promise<string> {
370+
// Resolve the checking promise to make sure it's finished.
371+
await this.checkTokenPluginFile(url);
372+
373+
// The previous promise (tokenPluginFileWorks) result will be used here.
374+
return this.fixPluginfileURL(url);
371375
}
372376

373377
/**
374378
* Generic function for adding the wstoken to Moodle urls and for pointing to the correct script.
375-
* Uses CoreUtilsProvider.fixPluginfileURL, passing site's token.
379+
* Uses CoreUrl.fixPluginfileURL, passing site's token.
376380
*
377381
* @param url The url to be fixed.
378382
* @returns Fixed URL.
@@ -386,17 +390,13 @@ export class CoreSite extends CoreAuthenticatedSite {
386390

387391
/**
388392
* Deletes site's DB.
389-
*
390-
* @returns Promise to be resolved when the DB is deleted.
391393
*/
392394
async deleteDB(): Promise<void> {
393395
await CoreDB.deleteDB('Site-' + this.id);
394396
}
395397

396398
/**
397399
* Deletes site's folder.
398-
*
399-
* @returns Promise to be resolved when the DB is deleted.
400400
*/
401401
async deleteFolder(): Promise<void> {
402402
if (!CoreFile.isAvailable() || !this.id) {
@@ -466,7 +466,6 @@ export class CoreSite extends CoreAuthenticatedSite {
466466
* @param url The URL to open.
467467
* @param alertMessage If defined, an alert will be shown before opening the browser.
468468
* @param options Other options.
469-
* @returns Promise resolved when done, rejected otherwise.
470469
*/
471470
async openInBrowserWithAutoLogin(
472471
url: string,
@@ -598,8 +597,6 @@ export class CoreSite extends CoreAuthenticatedSite {
598597

599598
/**
600599
* Invalidates config WS call.
601-
*
602-
* @returns Promise resolved when the data is invalidated.
603600
*/
604601
async invalidateConfig(): Promise<void> {
605602
await this.invalidateWsCacheForKey(this.getConfigCacheKey());
@@ -728,7 +725,6 @@ export class CoreSite extends CoreAuthenticatedSite {
728725
* Deletes a site setting.
729726
*
730727
* @param name The config name.
731-
* @returns Promise resolved when done.
732728
*/
733729
async deleteSiteConfig(name: string): Promise<void> {
734730
await this.configTable.deleteByPrimaryKey({ name });
@@ -760,13 +756,12 @@ export class CoreSite extends CoreAuthenticatedSite {
760756
*
761757
* @param name The config name.
762758
* @param value The config value. Can only store number or strings.
763-
* @returns Promise resolved when done.
764759
*/
765760
async setLocalSiteConfig(name: string, value: number | string): Promise<void> {
766761
await this.configTable.insert({ name, value });
767762
}
768763

769-
/*
764+
/**
770765
* Check if tokenpluginfile script works in the site.
771766
*
772767
* @param url URL to check.
@@ -802,7 +797,6 @@ export class CoreSite extends CoreAuthenticatedSite {
802797
* Deletes last viewed records based on some conditions.
803798
*
804799
* @param conditions Conditions.
805-
* @returns Promise resolved when done.
806800
*/
807801
async deleteLastViewed(conditions?: Partial<CoreSiteLastViewedDBRecord>): Promise<void> {
808802
await this.lastViewedTable.delete(conditions);
@@ -853,7 +847,6 @@ export class CoreSite extends CoreAuthenticatedSite {
853847
* @param id ID.
854848
* @param value Last viewed item value.
855849
* @param options Options.
856-
* @returns Promise resolved when done.
857850
*/
858851
async storeLastViewed(
859852
component: string,

src/core/features/login/components/components.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { NgModule } from '@angular/core';
1616
import { CoreSharedModule } from '@/core/shared.module';
1717
import { CoreLoginMethodsComponent } from './login-methods/login-methods';
1818
import { CoreLoginExceededAttemptsComponent } from '@features/login/components/exceeded-attempts/exceeded-attempts';
19+
import { CoreLoginIdentityProviderComponent } from './identity-provider/identity-provider';
1920

2021
@NgModule({
2122
declarations: [
@@ -24,6 +25,7 @@ import { CoreLoginExceededAttemptsComponent } from '@features/login/components/e
2425
],
2526
imports: [
2627
CoreSharedModule,
28+
CoreLoginIdentityProviderComponent,
2729
],
2830
exports: [
2931
CoreLoginExceededAttemptsComponent,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<ion-button class="ion-text-wrap ion-margin core-oauth-provider" (click)="openOAuth()" [ariaLabel]="provider.name" expand="block"
2+
fill="outline">
3+
@if (provider.iconurl) {
4+
<img [src]="provider.iconurl" alt="" width="32" height="32" slot="start" aria-hidden="true" (error)="provider.iconurl = ''" />
5+
}
6+
<ion-label>{{ provider.name }}</ion-label>
7+
</ion-button>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { Component, Input } from '@angular/core';
16+
import { CoreSiteIdentityProvider } from '@classes/sites/unauthenticated-site';
17+
import { CoreLoginHelper } from '@features/login/services/login-helper';
18+
import { CoreDomUtils } from '@services/utils/dom';
19+
import { CoreSharedModule } from '@/core/shared.module';
20+
import { CoreRedirectPayload } from '@services/navigator';
21+
22+
@Component({
23+
selector: 'core-identity-provider',
24+
templateUrl: 'identity-provider.html',
25+
standalone: true,
26+
imports: [
27+
CoreSharedModule,
28+
],
29+
})
30+
export class CoreLoginIdentityProviderComponent {
31+
32+
@Input({ required: true }) provider!: CoreSiteIdentityProvider;
33+
@Input() launchurl = '';
34+
@Input() siteUrl = '';
35+
@Input() redirectData?: CoreRedirectPayload;
36+
37+
/**
38+
* The button has been clicked.
39+
*/
40+
async openOAuth(): Promise<void> {
41+
const result = await CoreLoginHelper.openBrowserForOAuthLogin(
42+
this.siteUrl,
43+
this.provider,
44+
this.launchurl,
45+
this.redirectData,
46+
);
47+
48+
if (!result) {
49+
CoreDomUtils.showErrorModal('Invalid data.');
50+
}
51+
}
52+
53+
}

src/core/features/login/components/login-methods/login-methods.html

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121
<!-- Identity providers. -->
2222
<ion-list *ngIf="identityProviders.length" class="core-login-identity-providers">
2323
<h2 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h2>
24-
<ion-button [fill]="'outline'" *ngFor="let provider of identityProviders" class="ion-text-wrap ion-margin core-oauth-provider"
25-
(click)="oauthClicked(provider)" [ariaLabel]="provider.name" expand="block">
26-
<img *ngIf="provider.iconurl" [src]="provider.iconurl" alt="" width="32" height="32" slot="start" aria-hidden="true">
27-
<ion-label>{{ provider.name }}</ion-label>
28-
</ion-button>
24+
<core-identity-provider *ngFor="let provider of identityProviders" [provider]="provider" [launchurl]="siteConfig?.launchurl"
25+
[redirectData]="redirectData" [siteUrl]="siteUrl" />
2926
</ion-list>

src/core/features/login/components/login-methods/login-methods.ts

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import { toBoolean } from '@/core/transforms/boolean';
1615
import { Component, Input, OnInit } from '@angular/core';
16+
import { CorePromisedValue } from '@classes/promised-value';
17+
import { CoreSite } from '@classes/sites/site';
1718
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/sites/unauthenticated-site';
1819
import { CoreLoginHelper, CoreLoginMethod } from '@features/login/services/login-helper';
1920
import { CoreRedirectPayload } from '@services/navigator';
20-
import { CoreSites } from '@services/sites';
2121
import { CoreSitesFactory } from '@services/sites-factory';
22-
import { CoreDomUtils } from '@services/utils/dom';
2322

2423
@Component({
2524
selector: 'core-login-methods',
@@ -28,27 +27,31 @@ import { CoreDomUtils } from '@services/utils/dom';
2827
})
2928
export class CoreLoginMethodsComponent implements OnInit {
3029

31-
@Input({ transform: toBoolean }) reconnect = false;
3230
@Input() siteUrl = '';
3331
@Input() siteConfig?: CoreSitePublicConfigResponse;
3432
@Input() redirectData?: CoreRedirectPayload;
33+
@Input() site?: CoreSite; // Defined when the user is reconnecting.
3534
@Input() showLoginForm = true;
3635

3736
isBrowserSSO = false;
3837
showScanQR = false;
3938
loginMethods: CoreLoginMethod[] = [];
4039
identityProviders: CoreSiteIdentityProvider[] = [];
4140

41+
protected currentLoginProvider?: CoreSiteIdentityProvider;
42+
protected isReady = new CorePromisedValue<void>();
43+
4244
/**
4345
* @inheritdoc
4446
*/
4547
async ngOnInit(): Promise<void> {
46-
if (this.reconnect) {
48+
if (this.site) {
49+
this.siteUrl = this.site.getURL();
50+
4751
this.loginMethods = await CoreLoginHelper.getLoginMethods();
4852

49-
const currentSite = CoreSites.getCurrentSite();
5053
const defaultMethod = await CoreLoginHelper.getDefaultLoginMethod();
51-
if (currentSite?.isLoggedOut() && defaultMethod) {
54+
if (this.site.isLoggedOut() && defaultMethod) {
5255
await defaultMethod.action();
5356
}
5457
}
@@ -59,25 +62,29 @@ export class CoreLoginMethodsComponent implements OnInit {
5962
// Identity providers won't be shown if login on browser.
6063
if (!this.isBrowserSSO) {
6164
this.identityProviders = await CoreLoginHelper.getValidIdentityProvidersForSite(
62-
CoreSitesFactory.makeUnauthenticatedSite(this.siteUrl, this.siteConfig),
65+
this.site ?? CoreSitesFactory.makeUnauthenticatedSite(this.siteUrl, this.siteConfig),
6366
);
6467
}
6568

66-
if (this.reconnect) {
69+
if (this.site) {
6770
this.showScanQR = CoreLoginHelper.displayQRInSiteScreen();
71+
72+
// The identity provider set in the site will be shown at the top.
73+
const oAuthId = this.site.getOAuthId();
74+
this.currentLoginProvider = CoreLoginHelper.findIdentityProvider(this.identityProviders, oAuthId);
6875
}
6976

7077
// If still false or credentials screen.
71-
if (!this.reconnect || !this.showScanQR) {
78+
if (!this.site || !this.showScanQR) {
7279
this.showScanQR = await CoreLoginHelper.displayQRInCredentialsScreen(this.siteConfig.tool_mobile_qrcodetype);
7380
}
7481
}
82+
83+
this.isReady.resolve();
7584
}
7685

7786
/**
7887
* Show instructions and scan QR code.
79-
*
80-
* @returns Promise resolved when done.
8188
*/
8289
async showInstructionsAndScanQR(): Promise<void> {
8390
try {
@@ -90,21 +97,33 @@ export class CoreLoginMethodsComponent implements OnInit {
9097
}
9198

9299
/**
93-
* An OAuth button was clicked.
100+
* Get the current login, removing the identity provider from the list.
94101
*
95-
* @param provider The provider that was clicked.
102+
* @returns Current login.
96103
*/
97-
async oauthClicked(provider: CoreSiteIdentityProvider): Promise<void> {
98-
const result = await CoreLoginHelper.openBrowserForOAuthLogin(
99-
this.siteUrl,
100-
provider,
101-
this.siteConfig?.launchurl,
102-
this.redirectData,
103-
);
104-
105-
if (!result) {
106-
CoreDomUtils.showErrorModal('Invalid data.');
104+
async extractCurrentLogin(): Promise<CoreLoginMethodsCurrentLogin | undefined> {
105+
await this.isReady;
106+
107+
if (!this.currentLoginProvider) {
108+
return;
107109
}
110+
111+
// Remove the identity provider from the array.
112+
this.identityProviders = this.identityProviders.filter((provider) =>
113+
provider.url !== this.currentLoginProvider?.url);
114+
115+
const showOther = !!(this.showLoginForm || this.isBrowserSSO) &&
116+
!!(this.loginMethods.length || this.identityProviders.length || this.showScanQR);
117+
118+
return {
119+
provider: this.currentLoginProvider,
120+
showOther,
121+
};
108122
}
109123

110124
}
125+
126+
export type CoreLoginMethodsCurrentLogin = {
127+
provider: CoreSiteIdentityProvider;
128+
showOther: boolean;
129+
};

src/core/features/login/lang.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"missingfirstname": "Missing given name",
7272
"missinglastname": "Missing last name",
7373
"mobileservicesnotenabled": "Mobile services are not enabled on the site.",
74+
"morewaystologin": "More ways to log in",
7475
"mustconfirm": "You need to confirm your account",
7576
"newaccount": "New account",
7677
"notloggedin": "You need to be logged in.",

src/core/features/login/login-reconnect-lazy.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { CoreSharedModule } from '@/core/shared.module';
1919
import { CoreLoginComponentsModule } from '@features/login/components/components.module';
2020
import { CoreLoginReconnectPage } from '@features/login/pages/reconnect/reconnect';
2121
import { CoreSiteLogoComponent } from '@/core/components/site-logo/site-logo';
22+
import { CoreLoginIdentityProviderComponent } from './components/identity-provider/identity-provider';
2223

2324
const routes: Routes = [
2425
{
@@ -33,6 +34,7 @@ const routes: Routes = [
3334
CoreSharedModule,
3435
CoreLoginComponentsModule,
3536
CoreSiteLogoComponent,
37+
CoreLoginIdentityProviderComponent,
3638
],
3739
declarations: [
3840
CoreLoginReconnectPage,

0 commit comments

Comments
 (0)