diff --git a/config.xml b/config.xml index fac066d54..604862743 100644 --- a/config.xml +++ b/config.xml @@ -1,5 +1,5 @@ - + Moodle Moodle official app Moodle Mobile team @@ -241,7 +241,7 @@ - 3.9.0 + 3.9.1 YES diff --git a/desktop/assets/windows/AppXManifest.xml b/desktop/assets/windows/AppXManifest.xml index fc2427ea7..285654c06 100644 --- a/desktop/assets/windows/AppXManifest.xml +++ b/desktop/assets/windows/AppXManifest.xml @@ -6,7 +6,7 @@ + Version="3.9.1.0" /> Moodle Desktop Moodle Pty Ltd. diff --git a/package.json b/package.json index 8268eef27..1d3792c31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moodlemobile", - "version": "3.9.0", + "version": "3.9.1", "description": "The official app for Moodle.", "author": { "name": "Moodle Pty Ltd.", @@ -253,7 +253,7 @@ "category": "public.app-category.education", "icon": "resources/desktop/icon.icns", "target": "mas", - "bundleVersion": "3.9.0", + "bundleVersion": "3.9.1", "extendInfo": { "ElectronTeamID": "2NU57U5PAW" } diff --git a/scripts/lang_functions.php b/scripts/lang_functions.php index 5f46d4623..7025faa8e 100644 --- a/scripts/lang_functions.php +++ b/scripts/lang_functions.php @@ -120,8 +120,6 @@ function add_langs_to_config($langs, $config) { function get_langfolder($lang) { $folder = LANGPACKSFOLDER.'/'.str_replace('-', '_', $lang); if (!is_dir($folder) || !is_file($folder.'/langconfig.php')) { - echo "Cannot translate $folder, folder not found"; - return false; } @@ -173,6 +171,8 @@ function reset_translations_strings() { function build_lang($lang, $keys) { $langfoldername = get_langfolder($lang); if (!$langfoldername) { + echo "Cannot translate $lang, folder not found"; + return false; } @@ -182,15 +182,18 @@ function build_lang($lang, $keys) { $override_langfolder = false; } - $total = count ($keys); + $total = count($keys); $local = 0; - $string = get_translation_strings($langfoldername, 'langconfig'); - $parent = isset($string['parentlanguage']) ? $string['parentlanguage'] : ""; + $langparts = explode('-', $lang, 2); + $parentname = $langparts[0] ?? ""; + $parent = ""; echo "Processing $lang"; - if ($parent != "" && $parent != $lang) { - echo " ($parent)"; + // Check parent language exists. + if ($parentname != $lang && get_langfolder($parentname)) { + echo " ($parentname)"; + $parent = $parentname; } $langFile = false; @@ -247,6 +250,12 @@ function build_lang($lang, $keys) { $translations[$key] = html_entity_decode($text); } + if (!empty($parent)) { + $translations['core.parentlanguage'] = $parent; + } else if (isset($translations['core.parentlanguage'])) { + unset($translations['core.parentlanguage']); + } + // Sort and save. ksort($translations); file_put_contents(ASSETSPATH.$lang.'.json', str_replace('\/', '/', json_encode($translations, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT))); @@ -275,6 +284,8 @@ function progressbar($percentage) { function detect_lang($lang, $keys) { $langfoldername = get_langfolder($lang); if (!$langfoldername) { + echo "Cannot translate $lang, folder not found"; + return false; } diff --git a/src/assets/lang/ca.json b/src/assets/lang/ca.json index 9850d760f..e39398a3c 100644 --- a/src/assets/lang/ca.json +++ b/src/assets/lang/ca.json @@ -1913,7 +1913,6 @@ "core.openmodinbrowser": "Obre {{$a}} al navegador", "core.othergroups": "Altres grups", "core.pagea": "Pàgina {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "Utilitzeu el botó de baix per pagar i inscriure-us.", "core.percentagenumber": "{{$a}}%", "core.phone": "Telèfon", diff --git a/src/assets/lang/cs.json b/src/assets/lang/cs.json index 8b4fba344..e65bfcaf4 100644 --- a/src/assets/lang/cs.json +++ b/src/assets/lang/cs.json @@ -1909,7 +1909,6 @@ "core.openmodinbrowser": "Otevřít {{$a}} v prohlížeči", "core.othergroups": "Další skupiny", "core.pagea": "Stránka {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "Pomocí tlačítka níže můžete provést platbu a během několika minut se zapsat do kurzu!", "core.percentagenumber": "{{$a}}%", "core.phone": "Telefon", diff --git a/src/assets/lang/da.json b/src/assets/lang/da.json index d78659458..78425dbfb 100644 --- a/src/assets/lang/da.json +++ b/src/assets/lang/da.json @@ -1752,7 +1752,6 @@ "core.openinbrowser": "Åben i browser", "core.othergroups": "Andre grupper", "core.pagea": "Side {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "Brug knappen forneden til at betale og blive tilmeldt umiddelbart derefter.", "core.percentagenumber": "{{$a}}%", "core.phone": "Telefon", diff --git a/src/assets/lang/el.json b/src/assets/lang/el.json index 94393e640..46d5af178 100644 --- a/src/assets/lang/el.json +++ b/src/assets/lang/el.json @@ -1841,7 +1841,6 @@ "core.openinbrowser": "Ανοίξτε στον περιηγητή.", "core.othergroups": "Άλλες ομάδες", "core.pagea": "Σελίδα {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "Χρησιμοποιήστε το παρακάτω πλήκτρο για να πληρώσετε και να εγγραφείτε μέσα σε λίγα λεπτά!", "core.percentagenumber": "{{$a}}%", "core.phone": "Τηλέφωνο", diff --git a/src/assets/lang/en-us.json b/src/assets/lang/en-us.json index c306b3ea8..c8a3e9ae4 100644 --- a/src/assets/lang/en-us.json +++ b/src/assets/lang/en-us.json @@ -14,6 +14,7 @@ "core.listsep": ",", "core.login.loginsteps": "For full access to this site, you first need to create an account.", "core.notenrolledprofile": "This profile is not available because this user is not enrolled in this course.", + "core.parentlanguage": "en", "core.paymentinstant": "Use the button below to pay and be enrolled within minutes!", "core.settings.license": "License", "core.strftimedate": "%B %d, %Y", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index a14a93b8e..22e7bc534 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1557,6 +1557,7 @@ "core.errorsomedatanotdownloaded": "If you downloaded this activity, please notice that some data isn't downloaded during the download process for performance and data usage reasons.", "core.errorsync": "An error occurred while synchronising. Please try again.", "core.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.", + "core.errorurlschemeinvalidsite": "This site URL cannot be opened in this app.", "core.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.", "core.favourites": "Starred", "core.filename": "Filename", diff --git a/src/assets/lang/es-mx.json b/src/assets/lang/es-mx.json index eeb40e0b4..211c8d9ba 100644 --- a/src/assets/lang/es-mx.json +++ b/src/assets/lang/es-mx.json @@ -1913,7 +1913,7 @@ "core.openmodinbrowser": "Abrir {{$a}} en navegador", "core.othergroups": "Otros grupos", "core.pagea": "Página {{$a}}", - "core.parentlanguage": "", + "core.parentlanguage": "es", "core.paymentinstant": "¡Utilice el botón de abajo para pagar y poder inscribirse en minutos!", "core.percentagenumber": "{{$a}}%", "core.phone": "Teléfono", diff --git a/src/assets/lang/es.json b/src/assets/lang/es.json index f2d9473ad..89a2973e6 100644 --- a/src/assets/lang/es.json +++ b/src/assets/lang/es.json @@ -1913,7 +1913,6 @@ "core.openmodinbrowser": "Abrir {{$a}} en el navegador", "core.othergroups": "Otros grupos", "core.pagea": "Página {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "¡Utilice el botón de abajo para pagar y poder matricularse en minutos!", "core.percentagenumber": "{{$a}}%", "core.phone": "Teléfono", diff --git a/src/assets/lang/fr.json b/src/assets/lang/fr.json index 8d7cbd4ae..5204cf40f 100644 --- a/src/assets/lang/fr.json +++ b/src/assets/lang/fr.json @@ -1875,7 +1875,6 @@ "core.openinbrowser": "Ouvrir dans le navigateur", "core.othergroups": "Autres groupes", "core.pagea": "Page {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "Le bouton ci-dessous vous permet de payer et de vous inscrire en quelques minutes !", "core.percentagenumber": "{{$a}} %", "core.phone": "Téléphone", diff --git a/src/assets/lang/hu.json b/src/assets/lang/hu.json index a8c8d802b..fdbe03386 100644 --- a/src/assets/lang/hu.json +++ b/src/assets/lang/hu.json @@ -1521,7 +1521,6 @@ "core.online": "Online", "core.othergroups": "Egyéb csoportok", "core.pagea": "{{$a}} oldal", - "core.parentlanguage": "", "core.paymentinstant": "A fizetéshez és a perceken belüli beiratkozáshoz használja az alábbi gombot!", "core.phone": "Telefon", "core.pictureof": "Kép", diff --git a/src/assets/lang/it.json b/src/assets/lang/it.json index 1cc74ffd6..53a508359 100644 --- a/src/assets/lang/it.json +++ b/src/assets/lang/it.json @@ -1769,7 +1769,6 @@ "core.openinbrowser": "Apri nel browser", "core.othergroups": "Altri gruppi", "core.pagea": "Pagina {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "Utilizza il pulsante sottostante per pagare ed essere iscritto in pochi minuti!", "core.percentagenumber": "{{$a}}%", "core.phone": "Telefono", diff --git a/src/assets/lang/ja.json b/src/assets/lang/ja.json index 87f66ecec..3a85781f1 100644 --- a/src/assets/lang/ja.json +++ b/src/assets/lang/ja.json @@ -1717,7 +1717,6 @@ "core.openinbrowser": "ブラウザで開く", "core.othergroups": "他のグループ", "core.pagea": "ページ {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "下のボタンをお使いください。支払いおよび登録がすぐに完了します!", "core.percentagenumber": "{{$a}}%", "core.phone": "電話", diff --git a/src/assets/lang/ko.json b/src/assets/lang/ko.json index 0f58bea81..80a5fd419 100644 --- a/src/assets/lang/ko.json +++ b/src/assets/lang/ko.json @@ -1385,7 +1385,6 @@ "core.openfullimage": "전체 크기 이미지를 보려면 여기를 클릭하십시오.", "core.openinbrowser": "브라우저에서 열기", "core.pagea": "페이지 {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "신속하게 등록금 지불 및 등록을 마치려면 아래의 버튼을 사용하시오!", "core.percentagenumber": "{{$a}}%", "core.phone": "전화", diff --git a/src/assets/lang/lt.json b/src/assets/lang/lt.json index 7ecff4d19..bac7a4c27 100644 --- a/src/assets/lang/lt.json +++ b/src/assets/lang/lt.json @@ -1634,7 +1634,6 @@ "core.openinbrowser": "Atidaryti naršyklėje", "core.othergroups": "Kitos grupės", "core.pagea": "{{$a}} puslapis", - "core.parentlanguage": "", "core.paymentinstant": "Naudokite toliau pateiktą mygtuką, kad sumokėtumėte ir būtumėte įregistruoti per kelias minutes.", "core.percentagenumber": "{{$a}}%", "core.phone": "Telefonas", diff --git a/src/assets/lang/no.json b/src/assets/lang/no.json index 06d153ab7..b6182efb8 100644 --- a/src/assets/lang/no.json +++ b/src/assets/lang/no.json @@ -1576,7 +1576,6 @@ "core.online": "På nett", "core.othergroups": "Andre grupper", "core.pagea": "Side {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "Bruk knappen under for å betale og melde deg på kurset.", "core.phone": "Telefon", "core.pictureof": "Bilde av {{$a}}", diff --git a/src/assets/lang/pt-br.json b/src/assets/lang/pt-br.json index 3e9127250..59c1192ae 100644 --- a/src/assets/lang/pt-br.json +++ b/src/assets/lang/pt-br.json @@ -1809,7 +1809,7 @@ "core.openinbrowser": "Abrir no navegador", "core.othergroups": "Outros grupos", "core.pagea": "Página {{$a}}", - "core.parentlanguage": "", + "core.parentlanguage": "pt", "core.paymentinstant": "Clique o botão abaixo para efetuar o pagamento e fazer a sua inscrição em poucos minutos!", "core.percentagenumber": "{{$a}}%", "core.phone": "Fone", diff --git a/src/assets/lang/pt.json b/src/assets/lang/pt.json index 3d497f8cb..d485e3fcd 100644 --- a/src/assets/lang/pt.json +++ b/src/assets/lang/pt.json @@ -1913,7 +1913,6 @@ "core.openmodinbrowser": "Abrir {{$a}} no navegador", "core.othergroups": "Outros grupos", "core.pagea": "Página {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "Use o botão abaixo para pagar e completar a inscrição!", "core.percentagenumber": "{{$a}}%", "core.phone": "Telefone", diff --git a/src/assets/lang/sl.json b/src/assets/lang/sl.json index 9d131004d..cd4fb87e5 100644 --- a/src/assets/lang/sl.json +++ b/src/assets/lang/sl.json @@ -1841,7 +1841,6 @@ "core.openinbrowser": "Odpri v brskalniku", "core.othergroups": "Ostale skupine", "core.pagea": "Stran {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "Uporabite spodnje gumbe za plačilo in vpis v nekaj minutah!", "core.percentagenumber": "{{$a}}%", "core.phone": "Telefon", diff --git a/src/assets/lang/sr-cr.json b/src/assets/lang/sr-cr.json index 4b9c044b4..d35fcfcd9 100644 --- a/src/assets/lang/sr-cr.json +++ b/src/assets/lang/sr-cr.json @@ -1700,7 +1700,6 @@ "core.openinbrowser": "Отвори у веб читачу", "core.othergroups": "Друге групе", "core.pagea": "Страница {{$a}}", - "core.parentlanguage": "en", "core.paymentinstant": "Употребите дугме испод како бисте извршили уплату и уписали курс у року од неколико минута!", "core.percentagenumber": "{{$a}}%", "core.phone": "Телефон", diff --git a/src/assets/lang/sr-lt.json b/src/assets/lang/sr-lt.json index b12f5347d..038d60dfc 100644 --- a/src/assets/lang/sr-lt.json +++ b/src/assets/lang/sr-lt.json @@ -1700,7 +1700,6 @@ "core.openinbrowser": "Otvori u veb čitaču", "core.othergroups": "Druge grupe", "core.pagea": "Stranica {{$a}}", - "core.parentlanguage": "en", "core.paymentinstant": "Upotrebite dugme ispod kako biste izvršili uplatu i upisali kurs u roku od nekoliko minuta!", "core.percentagenumber": "{{$a}}%", "core.phone": "Telefon", diff --git a/src/assets/lang/zh-cn.json b/src/assets/lang/zh-cn.json index 2948d13a4..77906091f 100644 --- a/src/assets/lang/zh-cn.json +++ b/src/assets/lang/zh-cn.json @@ -1844,7 +1844,6 @@ "core.openinbrowser": "在浏览器中打开", "core.othergroups": "其他小组", "core.pagea": "页 {{$a}}", - "core.parentlanguage": "", "core.paymentinstant": "点击下面的按钮便可以快速付费并加入课程!", "core.phone": "电话", "core.pictureof": "{{$a}}的头像", diff --git a/src/assets/lang/zh-tw.json b/src/assets/lang/zh-tw.json index 44bc37dea..50fadac48 100644 --- a/src/assets/lang/zh-tw.json +++ b/src/assets/lang/zh-tw.json @@ -1636,7 +1636,6 @@ "core.openinbrowser": "以瀏覽器開啟", "core.othergroups": "其他群組", "core.pagea": "第 {{$a}} 頁", - "core.parentlanguage": "", "core.paymentinstant": "使用以下按鈕立即付款及註冊。", "core.percentagenumber": "{{$a}}%", "core.phone": "電話", diff --git a/src/components/loading/loading.ts b/src/components/loading/loading.ts index 4d658fbd6..2685a5b48 100644 --- a/src/components/loading/loading.ts +++ b/src/components/loading/loading.ts @@ -21,9 +21,9 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; /** * Component to show a loading spinner and message while data is being loaded. * - * It will show a spinner with a message and hide all the content until 'dataLoaded' variable is set to true. - * If 'message' and 'dynMessage' attributes aren't set, default message "Loading" is shown. - * 'message' attribute accepts hardcoded strings, variables, filters, etc. E.g. message="'core.loading' | translate". + * It will show a spinner with a message and hide all the content until 'hideUntil' variable is set to a truthy value (!!hideUntil). + * If 'message' isn't set, default message "Loading" is shown. + * 'message' attribute accepts hardcoded strings, variables, filters, etc. E.g. [message]="'core.loading' | translate". * * Usage: * @@ -44,7 +44,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; animations: [coreShowHideAnimation] }) export class CoreLoadingComponent implements OnInit, OnChanges { - @Input() hideUntil: boolean; // Determine when should the contents be shown. + @Input() hideUntil: any; // Determine when should the contents be shown. @Input() message?: string; // Message to show while loading. @ViewChild('content') content: ElementRef; @@ -69,7 +69,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges { } // Add class if loaded on init. - if (this.hideUntil) { + if (!!this.hideUntil) { this.element.classList.add('core-loading-loaded'); this.content.nativeElement.classList.add('core-loading-content'); } @@ -77,7 +77,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges { ngOnChanges(changes: { [name: string]: SimpleChange }): void { if (changes.hideUntil) { - if (changes.hideUntil.currentValue === true) { + if (!!this.hideUntil) { setTimeout(() => { // Content is loaded so, center the spinner on the content itself. this.element.classList.add('core-loading-loaded'); @@ -96,7 +96,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges { // Trigger the event after a timeout since the elements inside ngIf haven't been added to DOM yet. setTimeout(() => { this.eventsProvider.trigger(CoreEventsProvider.CORE_LOADING_CHANGED, { - loaded: changes.hideUntil.currentValue, + loaded: !!this.hideUntil, uniqueId: this.uniqueId }); }); diff --git a/src/config.json b/src/config.json index 7b6e032bd..b3b686edd 100644 --- a/src/config.json +++ b/src/config.json @@ -2,8 +2,8 @@ "app_id": "com.moodle.moodlemobile", "appname": "Moodle Mobile", "desktopappname": "Moodle Desktop", - "versioncode": 3900, - "versionname": "3.9.0", + "versioncode": 3910, + "versionname": "3.9.1", "cache_update_frequency_usually": 420000, "cache_update_frequency_often": 1200000, "cache_update_frequency_sometimes": 3600000, @@ -81,6 +81,7 @@ "siteurl": "", "sitename": "", "multisitesdisplay": "", + "onlyallowlistedsites": false, "skipssoconfirmation": false, "forcedefaultlanguage": false, "privacypolicy": "https:\/\/moodle.net\/moodle-app-privacy\/", @@ -104,4 +105,4 @@ "mac": "id1255924440", "linux": "https:\/\/download.moodle.org\/desktop\/download.php?platform=linux&arch=64" } -} \ No newline at end of file +} diff --git a/src/core/login/login.scss b/src/core/login/login.scss index 5b5ba112a..8206b3750 100644 --- a/src/core/login/login.scss +++ b/src/core/login/login.scss @@ -105,4 +105,10 @@ ion-app.app-root page-core-login-site { background: transparent; } } + + .core-login-site-qrcode-separator { + text-align: center; + margin-top: 12px; + font-size: 1.2em; + } } diff --git a/src/core/login/pages/credentials/credentials.html b/src/core/login/pages/credentials/credentials.html index eb2eab7ed..690b3d93b 100644 --- a/src/core/login/pages/credentials/credentials.html +++ b/src/core/login/pages/credentials/credentials.html @@ -34,6 +34,16 @@
+ + + + + diff --git a/src/core/login/pages/credentials/credentials.ts b/src/core/login/pages/credentials/credentials.ts index e614d1b8b..180aa315e 100644 --- a/src/core/login/pages/credentials/credentials.ts +++ b/src/core/login/pages/credentials/credentials.ts @@ -16,12 +16,14 @@ import { Component, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; +import { CoreUtils } from '@providers/utils/utils'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreLoginHelperProvider } from '../../providers/helper'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { CoreConfigConstants } from '../../../../configconstants'; +import { CoreCustomURLSchemes } from '@providers/urlschemes'; /** * Page to enter the user credentials. @@ -47,6 +49,7 @@ export class CoreLoginCredentialsPage { isBrowserSSO = false; isFixedUrlSet = false; showForgottenPassword = true; + showScanQR: boolean; protected siteConfig; protected eventThrown = false; @@ -74,6 +77,17 @@ export class CoreLoginCredentialsPage { username: [navParams.get('username') || '', Validators.required], password: ['', Validators.required] }); + + const canScanQR = CoreUtils.instance.canScanQR(); + if (canScanQR) { + if (typeof CoreConfigConstants['displayqroncredentialscreen'] == 'undefined') { + this.showScanQR = this.loginHelper.isFixedUrlSet(); + } else { + this.showScanQR = !!CoreConfigConstants['displayqroncredentialscreen']; + } + } else { + this.showScanQR = false; + } } /** @@ -267,4 +281,46 @@ export class CoreLoginCredentialsPage { signup(): void { this.navCtrl.push('CoreLoginEmailSignupPage', { siteUrl: this.siteUrl }); } + + /** + * Show instructions and scan QR code. + */ + showInstructionsAndScanQR(): void { + // Show some instructions first. + this.domUtils.showAlertWithOptions({ + title: this.translate.instant('core.login.faqwhereisqrcode'), + message: this.translate.instant('core.login.faqwhereisqrcodeanswer', + {$image: CoreLoginHelperProvider.FAQ_QRCODE_IMAGE_HTML}), + buttons: [ + { + text: this.translate.instant('core.cancel'), + role: 'cancel' + }, + { + text: this.translate.instant('core.next'), + handler: (): void => { + this.scanQR(); + } + }, + ], + }); + } + + /** + * Scan a QR code and put its text in the URL input. + * + * @return Promise resolved when done. + */ + async scanQR(): Promise { + // Scan for a QR code. + const text = await CoreUtils.instance.scanQR(); + + if (text && CoreCustomURLSchemes.instance.isCustomURL(text)) { + try { + await CoreCustomURLSchemes.instance.handleCustomURL(text); + } catch (error) { + CoreCustomURLSchemes.instance.treatHandleCustomURLError(error); + } + } + } } diff --git a/src/core/login/pages/site/site.html b/src/core/login/pages/site/site.html index d5dc5c1d9..714f8ad38 100644 --- a/src/core/login/pages/site/site.html +++ b/src/core/login/pages/site/site.html @@ -14,76 +14,89 @@ -
+ - -

{{ 'core.login.siteaddress' | translate }}

- -
+ + +

{{ 'core.login.siteaddress' | translate }}

+ +
+
+ + +

{{ 'core.login.siteaddress' | translate }}

+ +
+ + + + +
- - - - - + {{ 'core.login.selectsite' | translate }} {{site.name}} - - - - - + + + - - -

{{ 'core.login.selectsite' | translate }}

- - -

{{site.name}}

-

{{site.url}}

-
-
+ + + +

{{ 'core.login.selectsite' | translate }}

+ + +

{{site.name}}

+

{{site.url}}

+
+
- -
-

{{ 'core.login.selectsite' | translate }}

- {{site.name}} -
+ +
+

{{ 'core.login.selectsite' | translate }}

+ {{site.name}} +
+
+ + + + + diff --git a/src/core/login/pages/site/site.scss b/src/core/login/pages/site/site.scss index cff2ef02e..bde235732 100644 --- a/src/core/login/pages/site/site.scss +++ b/src/core/login/pages/site/site.scss @@ -129,10 +129,4 @@ ion-app.app-root page-core-login-site { .core-login-default-icon { filter: grayscale(100%); } - - .core-login-site-qrcode-separator { - text-align: center; - margin-top: 12px; - font-size: 1.2em; - } } diff --git a/src/core/login/pages/site/site.ts b/src/core/login/pages/site/site.ts index d21809dd5..e9d43091b 100644 --- a/src/core/login/pages/site/site.ts +++ b/src/core/login/pages/site/site.ts @@ -53,7 +53,7 @@ export class CoreLoginSitePage { siteForm: FormGroup; fixedSites: CoreLoginSiteInfo[]; filteredSites: CoreLoginSiteInfo[]; - fixedDisplay = 'buttons'; + siteSelector = 'sitefinder'; showKeyboard = false; filter = ''; sites: CoreLoginSiteInfoExtended[] = []; @@ -80,17 +80,16 @@ export class CoreLoginSitePage { protected textUtils: CoreTextUtilsProvider) { this.showKeyboard = !!navParams.get('showKeyboard'); - this.showScanQR = this.utils.canScanQR(); let url = ''; + this.siteSelector = CoreConfigConstants.multisitesdisplay; // Load fixed sites if they're set. if (this.loginHelper.hasSeveralFixedSites()) { this.fixedSites = this.loginHelper.getFixedSites(); - this.fixedDisplay = CoreConfigConstants.multisitesdisplay; // Autoselect if not defined. - if (['list', 'listnourl', 'select', 'buttons'].indexOf(this.fixedDisplay) < 0) { - this.fixedDisplay = this.fixedSites.length > 8 ? 'list' : (this.fixedSites.length > 3 ? 'select' : 'buttons'); + if (['list', 'listnourl', 'select', 'buttons'].indexOf(this.siteSelector) < 0) { + this.siteSelector = this.fixedSites.length > 8 ? 'list' : (this.fixedSites.length > 3 ? 'select' : 'buttons'); } this.filteredSites = this.fixedSites; url = this.fixedSites[0].url; @@ -103,6 +102,9 @@ export class CoreLoginSitePage { }); } + this.showScanQR = this.utils.canScanQR() && (typeof CoreConfigConstants['displayqronsitescreen'] == 'undefined' || + !!CoreConfigConstants['displayqronsitescreen']); + this.siteForm = fb.group({ siteUrl: [url, this.moodleUrlValidator()] }); diff --git a/src/core/mainmenu/pages/more/more.ts b/src/core/mainmenu/pages/more/more.ts index cca0add21..a3b786c07 100644 --- a/src/core/mainmenu/pages/more/more.ts +++ b/src/core/mainmenu/pages/more/more.ts @@ -67,7 +67,8 @@ export class CoreMainMenuMorePage implements OnDestroy { this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.loadSiteInfo.bind(this), sitesProvider.getCurrentSiteId()); this.loadSiteInfo(); - this.showScanQR = this.utils.canScanQR(); + this.showScanQR = this.utils.canScanQR() && + !this.sitesProvider.getCurrentSite().isFeatureDisabled('CoreMainMenuDelegate_QrReader'); } /** diff --git a/src/lang/en.json b/src/lang/en.json index 64b9a3172..68341442e 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -102,6 +102,7 @@ "errorsomedatanotdownloaded": "If you downloaded this activity, please notice that some data isn't downloaded during the download process for performance and data usage reasons.", "errorsync": "An error occurred while synchronising. Please try again.", "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.", + "errorurlschemeinvalidsite": "This site URL cannot be opened in this app.", "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.", "favourites": "Starred", "filename": "Filename", diff --git a/src/providers/lang.ts b/src/providers/lang.ts index b598be1b7..7454acd7d 100644 --- a/src/providers/lang.ts +++ b/src/providers/lang.ts @@ -55,7 +55,9 @@ export class CoreLangProvider { translate.onLangChange.subscribe((event: any) => { platform.setLang(event.lang, true); - platform.setDir(this.translate.instant('core.thisdirection'), true); + + const dir = this.translate.instant('core.thisdirection'); + platform.setDir(dir.indexOf('rtl') != -1 ? 'rtl' : 'ltr', true); }); } diff --git a/src/providers/urlschemes.ts b/src/providers/urlschemes.ts index 577b9d05b..20e466e81 100644 --- a/src/providers/urlschemes.ts +++ b/src/providers/urlschemes.ts @@ -29,6 +29,7 @@ import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins import { CoreConfigConstants } from '../configconstants'; import { CoreConstants } from '@core/constants'; import { makeSingleton } from '@singletons/core.singletons'; +import { CoreUrl } from '@singletons/url'; /** * All params that can be in a custom URL scheme. @@ -166,6 +167,12 @@ export class CoreCustomURLSchemesProvider { } try { + const isValid = await this.isInFixedSiteUrls(data.siteUrl); + + if (!isValid) { + throw this.translate.instant('core.errorurlschemeinvalidsite'); + } + if (data.redirect && data.redirect.match(/^https?:\/\//) && data.redirect.indexOf(data.siteUrl) == -1) { // Redirect URL must belong to the same site. Reject. throw this.translate.instant('core.contentlinks.errorredirectothersite'); @@ -540,6 +547,38 @@ export class CoreCustomURLSchemesProvider { this.domUtils.showErrorModalDefault(error.error, this.translate.instant('core.login.invalidsite')); } } + + /** + * Check if a site URL is one of the fixed sites for the app (in case there are fixed sites). + * + * @param siteUrl Site URL to check. + * @return Promise resolved with boolean: whether is one of the fixed sites. + */ + protected async isInFixedSiteUrls(siteUrl: string): Promise { + if (this.loginHelper.isFixedUrlSet()) { + + return CoreUrl.sameDomainAndPath(siteUrl, this.loginHelper.getFixedSites()); + } else if (this.loginHelper.hasSeveralFixedSites()) { + const sites = this.loginHelper.getFixedSites(); + + const site = sites.find((site) => { + return CoreUrl.sameDomainAndPath(siteUrl, site.url); + }); + + return !!site; + } else if (CoreConfigConstants.multisitesdisplay == 'sitefinder' && CoreConfigConstants.onlyallowlistedsites) { + // Call the sites finder to validate the site. + const result = await this.sitesProvider.findSites(siteUrl.replace(/^https?\:\/\/|\.\w{2,3}\/?$/g, '')); + + const site = result && result.find((site) => { + return CoreUrl.sameDomainAndPath(siteUrl, site.url); + }); + + return !!site; + } + + return true; + } } /** diff --git a/src/providers/ws.ts b/src/providers/ws.ts index 97f8ee323..86fd9070a 100644 --- a/src/providers/ws.ts +++ b/src/providers/ws.ts @@ -162,7 +162,7 @@ export class CoreWSProvider { this.logger = logger.getInstance('CoreWSProvider'); platform.ready().then(() => { - if (this.appProvider.isMobile()) { + if (this.appProvider.isIOS()) { ( cordova).plugin.http.setHeader('User-Agent', navigator.userAgent); } }); diff --git a/src/singletons/url.ts b/src/singletons/url.ts index cd1798c79..019e893c2 100644 --- a/src/singletons/url.ts +++ b/src/singletons/url.ts @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { CoreTextUtils } from '@providers/utils/text'; + /** * Parts contained within a url. */ @@ -172,4 +174,27 @@ export class CoreUrl { static removeProtocol(url: string): string { return url.replace(/^[a-zA-Z]+:\/\//i, ''); } + + /** + * Check if two URLs have the same domain and path. + * + * @param urlA First URL. + * @param urlB Second URL. + * @return Whether they have same domain and path. + */ + static sameDomainAndPath(urlA: string, urlB: string): boolean { + // Add protocol if missing, the parse function requires it. + if (!urlA.match(/^[^\/:\.\?]*:\/\//)) { + urlA = `https://${urlA}`; + } + if (!urlB.match(/^[^\/:\.\?]*:\/\//)) { + urlB = `https://${urlB}`; + } + + const partsA = CoreUrl.parse(urlA); + const partsB = CoreUrl.parse(urlB); + + return partsA.domain == partsB.domain && + CoreTextUtils.instance.removeEndingSlash(partsA.path) == CoreTextUtils.instance.removeEndingSlash(partsB.path); + } }