Skip to content

Commit 5ab88ad

Browse files
authored
Merge pull request #2431 from dpalou/MOBILE-3458
Mobile 3458
2 parents 055ba34 + d52392f commit 5ab88ad

File tree

10 files changed

+152
-19
lines changed

10 files changed

+152
-19
lines changed

src/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"siteurl": "",
8282
"sitename": "",
8383
"multisitesdisplay": "",
84+
"onlyallowlistedsites": false,
8485
"skipssoconfirmation": false,
8586
"forcedefaultlanguage": false,
8687
"privacypolicy": "https:\/\/moodle.net\/moodle-app-privacy\/",
@@ -104,4 +105,4 @@
104105
"mac": "id1255924440",
105106
"linux": "https:\/\/download.moodle.org\/desktop\/download.php?platform=linux&arch=64"
106107
}
107-
}
108+
}

src/core/login/login.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,10 @@ ion-app.app-root page-core-login-site {
105105
background: transparent;
106106
}
107107
}
108+
109+
.core-login-site-qrcode-separator {
110+
text-align: center;
111+
margin-top: 12px;
112+
font-size: 1.2em;
113+
}
108114
}

src/core/login/pages/credentials/credentials.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ <h3 *ngIf="siteName" padding class="core-sitename"><core-format-text [text]="sit
3434
<div padding>
3535
<button ion-button block [disabled]="siteChecked && !isBrowserSSO && !credForm.valid" class="core-login-login-button">{{ 'core.login.loginbutton' | translate }}</button>
3636
</div>
37+
38+
<ng-container *ngIf="showScanQR">
39+
<div class="core-login-site-qrcode-separator">{{ 'core.login.or' | translate }}</div>
40+
<ion-item class="core-login-site-qrcode">
41+
<a ion-button block color="light" margin-top icon-start (click)="showInstructionsAndScanQR()">
42+
<core-icon name="fa-qrcode" aria-hidden="true"></core-icon>
43+
{{ 'core.scanqr' | translate }}
44+
</a>
45+
</ion-item>
46+
</ng-container>
3747
</form>
3848

3949
<!-- Forgotten password button. -->

src/core/login/pages/credentials/credentials.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ import { Component, ViewChild, ElementRef } from '@angular/core';
1616
import { IonicPage, NavController, NavParams } from 'ionic-angular';
1717
import { TranslateService } from '@ngx-translate/core';
1818
import { CoreAppProvider } from '@providers/app';
19+
import { CoreUtils } from '@providers/utils/utils';
1920
import { CoreEventsProvider } from '@providers/events';
2021
import { CoreSitesProvider } from '@providers/sites';
2122
import { CoreDomUtilsProvider } from '@providers/utils/dom';
2223
import { CoreLoginHelperProvider } from '../../providers/helper';
2324
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
2425
import { CoreConfigConstants } from '../../../../configconstants';
26+
import { CoreCustomURLSchemes } from '@providers/urlschemes';
2527

2628
/**
2729
* Page to enter the user credentials.
@@ -47,6 +49,7 @@ export class CoreLoginCredentialsPage {
4749
isBrowserSSO = false;
4850
isFixedUrlSet = false;
4951
showForgottenPassword = true;
52+
showScanQR: boolean;
5053

5154
protected siteConfig;
5255
protected eventThrown = false;
@@ -74,6 +77,17 @@ export class CoreLoginCredentialsPage {
7477
username: [navParams.get('username') || '', Validators.required],
7578
password: ['', Validators.required]
7679
});
80+
81+
const canScanQR = CoreUtils.instance.canScanQR();
82+
if (canScanQR) {
83+
if (typeof CoreConfigConstants['displayqroncredentialscreen'] == 'undefined') {
84+
this.showScanQR = this.loginHelper.isFixedUrlSet();
85+
} else {
86+
this.showScanQR = !!CoreConfigConstants['displayqroncredentialscreen'];
87+
}
88+
} else {
89+
this.showScanQR = false;
90+
}
7791
}
7892

7993
/**
@@ -267,4 +281,46 @@ export class CoreLoginCredentialsPage {
267281
signup(): void {
268282
this.navCtrl.push('CoreLoginEmailSignupPage', { siteUrl: this.siteUrl });
269283
}
284+
285+
/**
286+
* Show instructions and scan QR code.
287+
*/
288+
showInstructionsAndScanQR(): void {
289+
// Show some instructions first.
290+
this.domUtils.showAlertWithOptions({
291+
title: this.translate.instant('core.login.faqwhereisqrcode'),
292+
message: this.translate.instant('core.login.faqwhereisqrcodeanswer',
293+
{$image: CoreLoginHelperProvider.FAQ_QRCODE_IMAGE_HTML}),
294+
buttons: [
295+
{
296+
text: this.translate.instant('core.cancel'),
297+
role: 'cancel'
298+
},
299+
{
300+
text: this.translate.instant('core.next'),
301+
handler: (): void => {
302+
this.scanQR();
303+
}
304+
},
305+
],
306+
});
307+
}
308+
309+
/**
310+
* Scan a QR code and put its text in the URL input.
311+
*
312+
* @return Promise resolved when done.
313+
*/
314+
async scanQR(): Promise<void> {
315+
// Scan for a QR code.
316+
const text = await CoreUtils.instance.scanQR();
317+
318+
if (text && CoreCustomURLSchemes.instance.isCustomURL(text)) {
319+
try {
320+
await CoreCustomURLSchemes.instance.handleCustomURL(text);
321+
} catch (error) {
322+
CoreCustomURLSchemes.instance.treatHandleCustomURLError(error);
323+
}
324+
}
325+
}
270326
}

src/core/login/pages/site/site.html

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,6 @@ <h2 text-wrap>{{site.name}}<ng-container *ngIf="site.alias"> ({{site.alias}})</n
5656
<ion-option *ngFor="let site of fixedSites" [value]="site.url">{{site.name}}</ion-option>
5757
</ion-select>
5858
</ion-item>
59-
60-
<ng-container *ngIf="!fixedSites && showScanQR && !hasSites && !enteredSiteUrl">
61-
<div class="core-login-site-qrcode-separator">{{ 'core.login.or' | translate }}</div>
62-
<ion-item class="core-login-site-qrcode">
63-
<a ion-button block color="light" margin-top icon-start (click)="showInstructionsAndScanQR()">
64-
<core-icon name="fa-qrcode" aria-hidden="true"></core-icon>
65-
{{ 'core.scanqr' | translate }}
66-
</a>
67-
</ion-item>
68-
</ng-container>
69-
7059
</form>
7160

7261
<!-- Pick the site from a list of fixed sites. -->
@@ -85,6 +74,16 @@ <h2>{{site.name}}</h2>
8574
<a *ngFor="let site of fixedSites" ion-button block (click)="connect($event, site.url)" [title]="site.name" margin-bottom>{{site.name}}</a>
8675
</div>
8776

77+
<ng-container *ngIf="showScanQR && !hasSites && !enteredSiteUrl">
78+
<div class="core-login-site-qrcode-separator">{{ 'core.login.or' | translate }}</div>
79+
<ion-item class="core-login-site-qrcode">
80+
<a ion-button block color="light" margin-top icon-start (click)="showInstructionsAndScanQR()">
81+
<core-icon name="fa-qrcode" aria-hidden="true"></core-icon>
82+
{{ 'core.scanqr' | translate }}
83+
</a>
84+
</ion-item>
85+
</ng-container>
86+
8887
<!-- Help. -->
8988
<ion-list no-lines margin-top>
9089
<a ion-item text-center text-wrap class="core-login-need-help" (click)="showHelp()" detail-none>

src/core/login/pages/site/site.scss

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,4 @@ ion-app.app-root page-core-login-site {
129129
.core-login-default-icon {
130130
filter: grayscale(100%);
131131
}
132-
133-
.core-login-site-qrcode-separator {
134-
text-align: center;
135-
margin-top: 12px;
136-
font-size: 1.2em;
137-
}
138132
}

src/core/login/pages/site/site.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ export class CoreLoginSitePage {
8080
protected textUtils: CoreTextUtilsProvider) {
8181

8282
this.showKeyboard = !!navParams.get('showKeyboard');
83-
this.showScanQR = this.utils.canScanQR();
8483

8584
let url = '';
8685

@@ -103,6 +102,9 @@ export class CoreLoginSitePage {
103102
});
104103
}
105104

105+
this.showScanQR = this.utils.canScanQR() && (typeof CoreConfigConstants['displayqronsitescreen'] == 'undefined' ||
106+
!!CoreConfigConstants['displayqronsitescreen']);
107+
106108
this.siteForm = fb.group({
107109
siteUrl: [url, this.moodleUrlValidator()]
108110
});

src/lang/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
"errorsomedatanotdownloaded": "If you downloaded this activity, please notice that some data isn't downloaded during the download process for performance and data usage reasons.",
103103
"errorsync": "An error occurred while synchronising. Please try again.",
104104
"errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.",
105+
"errorurlschemeinvalidsite": "This site URL cannot be opened in this app.",
105106
"explanationdigitalminor": "This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.",
106107
"favourites": "Starred",
107108
"filename": "Filename",

src/providers/urlschemes.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins
2929
import { CoreConfigConstants } from '../configconstants';
3030
import { CoreConstants } from '@core/constants';
3131
import { makeSingleton } from '@singletons/core.singletons';
32+
import { CoreUrl } from '@singletons/url';
3233

3334
/**
3435
* All params that can be in a custom URL scheme.
@@ -166,6 +167,12 @@ export class CoreCustomURLSchemesProvider {
166167
}
167168

168169
try {
170+
const isValid = await this.isInFixedSiteUrls(data.siteUrl);
171+
172+
if (!isValid) {
173+
throw this.translate.instant('core.errorurlschemeinvalidsite');
174+
}
175+
169176
if (data.redirect && data.redirect.match(/^https?:\/\//) && data.redirect.indexOf(data.siteUrl) == -1) {
170177
// Redirect URL must belong to the same site. Reject.
171178
throw this.translate.instant('core.contentlinks.errorredirectothersite');
@@ -540,6 +547,38 @@ export class CoreCustomURLSchemesProvider {
540547
this.domUtils.showErrorModalDefault(error.error, this.translate.instant('core.login.invalidsite'));
541548
}
542549
}
550+
551+
/**
552+
* Check if a site URL is one of the fixed sites for the app (in case there are fixed sites).
553+
*
554+
* @param siteUrl Site URL to check.
555+
* @return Promise resolved with boolean: whether is one of the fixed sites.
556+
*/
557+
protected async isInFixedSiteUrls(siteUrl: string): Promise<boolean> {
558+
if (this.loginHelper.isFixedUrlSet()) {
559+
560+
return CoreUrl.sameDomainAndPath(siteUrl, <string> this.loginHelper.getFixedSites());
561+
} else if (this.loginHelper.hasSeveralFixedSites()) {
562+
const sites = <any[]> this.loginHelper.getFixedSites();
563+
564+
const site = sites.find((site) => {
565+
return CoreUrl.sameDomainAndPath(siteUrl, site.url);
566+
});
567+
568+
return !!site;
569+
} else if (CoreConfigConstants.multisitesdisplay == 'sitefinder' && CoreConfigConstants.onlyallowlistedsites) {
570+
// Call the sites finder to validate the site.
571+
const result = await this.sitesProvider.findSites(siteUrl.replace(/^https?\:\/\/|\.\w{2,3}\/?$/g, ''));
572+
573+
const site = result && result.find((site) => {
574+
return CoreUrl.sameDomainAndPath(siteUrl, site.url);
575+
});
576+
577+
return !!site;
578+
}
579+
580+
return true;
581+
}
543582
}
544583

545584
/**

src/singletons/url.ts

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

15+
import { CoreTextUtils } from '@providers/utils/text';
16+
1517
/**
1618
* Parts contained within a url.
1719
*/
@@ -172,4 +174,27 @@ export class CoreUrl {
172174
static removeProtocol(url: string): string {
173175
return url.replace(/^[a-zA-Z]+:\/\//i, '');
174176
}
177+
178+
/**
179+
* Check if two URLs have the same domain and path.
180+
*
181+
* @param urlA First URL.
182+
* @param urlB Second URL.
183+
* @return Whether they have same domain and path.
184+
*/
185+
static sameDomainAndPath(urlA: string, urlB: string): boolean {
186+
// Add protocol if missing, the parse function requires it.
187+
if (!urlA.match(/^[^\/:\.\?]*:\/\//)) {
188+
urlA = `https://${urlA}`;
189+
}
190+
if (!urlB.match(/^[^\/:\.\?]*:\/\//)) {
191+
urlB = `https://${urlB}`;
192+
}
193+
194+
const partsA = CoreUrl.parse(urlA);
195+
const partsB = CoreUrl.parse(urlB);
196+
197+
return partsA.domain == partsB.domain &&
198+
CoreTextUtils.instance.removeEndingSlash(partsA.path) == CoreTextUtils.instance.removeEndingSlash(partsB.path);
199+
}
175200
}

0 commit comments

Comments
 (0)