commit
fb047a6e35
|
@ -1,5 +1,5 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<widget android-versionCode="39000" id="com.moodle.moodlemobile" ios-CFBundleVersion="3.9.0.0" version="3.9.0" versionCode="39000" xmlns="http://www.w3.org/ns/widgets" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
<widget android-versionCode="39000" id="com.moodle.moodlemobile" ios-CFBundleVersion="3.9.1.0" version="3.9.1" versionCode="39100" xmlns="http://www.w3.org/ns/widgets" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||||
<name>Moodle</name>
|
<name>Moodle</name>
|
||||||
<description>Moodle official app</description>
|
<description>Moodle official app</description>
|
||||||
<author email="mobile@moodle.com" href="http://moodle.com">Moodle Mobile team</author>
|
<author email="mobile@moodle.com" href="http://moodle.com">Moodle Mobile team</author>
|
||||||
|
@ -241,7 +241,7 @@
|
||||||
<true />
|
<true />
|
||||||
</edit-config>
|
</edit-config>
|
||||||
<edit-config file="*-Info.plist" mode="merge" target="CFBundleShortVersionString">
|
<edit-config file="*-Info.plist" mode="merge" target="CFBundleShortVersionString">
|
||||||
<string>3.9.0</string>
|
<string>3.9.1</string>
|
||||||
</edit-config>
|
</edit-config>
|
||||||
<config-file parent="FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED" target="*-Info.plist">
|
<config-file parent="FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED" target="*-Info.plist">
|
||||||
<string>YES</string>
|
<string>YES</string>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<Identity Name="3312ADB7.MoodleDesktop"
|
<Identity Name="3312ADB7.MoodleDesktop"
|
||||||
ProcessorArchitecture="x64"
|
ProcessorArchitecture="x64"
|
||||||
Publisher="CN=33CDCDF6-1EB5-4827-9897-ED25C91A32F6"
|
Publisher="CN=33CDCDF6-1EB5-4827-9897-ED25C91A32F6"
|
||||||
Version="3.9.0.0" />
|
Version="3.9.1.0" />
|
||||||
<Properties>
|
<Properties>
|
||||||
<DisplayName>Moodle Desktop</DisplayName>
|
<DisplayName>Moodle Desktop</DisplayName>
|
||||||
<PublisherDisplayName>Moodle Pty Ltd.</PublisherDisplayName>
|
<PublisherDisplayName>Moodle Pty Ltd.</PublisherDisplayName>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "moodlemobile",
|
"name": "moodlemobile",
|
||||||
"version": "3.9.0",
|
"version": "3.9.1",
|
||||||
"description": "The official app for Moodle.",
|
"description": "The official app for Moodle.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Moodle Pty Ltd.",
|
"name": "Moodle Pty Ltd.",
|
||||||
|
@ -253,7 +253,7 @@
|
||||||
"category": "public.app-category.education",
|
"category": "public.app-category.education",
|
||||||
"icon": "resources/desktop/icon.icns",
|
"icon": "resources/desktop/icon.icns",
|
||||||
"target": "mas",
|
"target": "mas",
|
||||||
"bundleVersion": "3.9.0",
|
"bundleVersion": "3.9.1",
|
||||||
"extendInfo": {
|
"extendInfo": {
|
||||||
"ElectronTeamID": "2NU57U5PAW"
|
"ElectronTeamID": "2NU57U5PAW"
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,8 +120,6 @@ function add_langs_to_config($langs, $config) {
|
||||||
function get_langfolder($lang) {
|
function get_langfolder($lang) {
|
||||||
$folder = LANGPACKSFOLDER.'/'.str_replace('-', '_', $lang);
|
$folder = LANGPACKSFOLDER.'/'.str_replace('-', '_', $lang);
|
||||||
if (!is_dir($folder) || !is_file($folder.'/langconfig.php')) {
|
if (!is_dir($folder) || !is_file($folder.'/langconfig.php')) {
|
||||||
echo "Cannot translate $folder, folder not found";
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,6 +171,8 @@ function reset_translations_strings() {
|
||||||
function build_lang($lang, $keys) {
|
function build_lang($lang, $keys) {
|
||||||
$langfoldername = get_langfolder($lang);
|
$langfoldername = get_langfolder($lang);
|
||||||
if (!$langfoldername) {
|
if (!$langfoldername) {
|
||||||
|
echo "Cannot translate $lang, folder not found";
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,15 +182,18 @@ function build_lang($lang, $keys) {
|
||||||
$override_langfolder = false;
|
$override_langfolder = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$total = count ($keys);
|
$total = count($keys);
|
||||||
$local = 0;
|
$local = 0;
|
||||||
|
|
||||||
$string = get_translation_strings($langfoldername, 'langconfig');
|
$langparts = explode('-', $lang, 2);
|
||||||
$parent = isset($string['parentlanguage']) ? $string['parentlanguage'] : "";
|
$parentname = $langparts[0] ?? "";
|
||||||
|
$parent = "";
|
||||||
|
|
||||||
echo "Processing $lang";
|
echo "Processing $lang";
|
||||||
if ($parent != "" && $parent != $lang) {
|
// Check parent language exists.
|
||||||
echo " ($parent)";
|
if ($parentname != $lang && get_langfolder($parentname)) {
|
||||||
|
echo " ($parentname)";
|
||||||
|
$parent = $parentname;
|
||||||
}
|
}
|
||||||
|
|
||||||
$langFile = false;
|
$langFile = false;
|
||||||
|
@ -247,6 +250,12 @@ function build_lang($lang, $keys) {
|
||||||
$translations[$key] = html_entity_decode($text);
|
$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.
|
// Sort and save.
|
||||||
ksort($translations);
|
ksort($translations);
|
||||||
file_put_contents(ASSETSPATH.$lang.'.json', str_replace('\/', '/', json_encode($translations, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)));
|
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) {
|
function detect_lang($lang, $keys) {
|
||||||
$langfoldername = get_langfolder($lang);
|
$langfoldername = get_langfolder($lang);
|
||||||
if (!$langfoldername) {
|
if (!$langfoldername) {
|
||||||
|
echo "Cannot translate $lang, folder not found";
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1913,7 +1913,6 @@
|
||||||
"core.openmodinbrowser": "Obre {{$a}} al navegador",
|
"core.openmodinbrowser": "Obre {{$a}} al navegador",
|
||||||
"core.othergroups": "Altres grups",
|
"core.othergroups": "Altres grups",
|
||||||
"core.pagea": "Pàgina {{$a}}",
|
"core.pagea": "Pàgina {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "Utilitzeu el botó de baix per pagar i inscriure-us.",
|
"core.paymentinstant": "Utilitzeu el botó de baix per pagar i inscriure-us.",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Telèfon",
|
"core.phone": "Telèfon",
|
||||||
|
|
|
@ -1909,7 +1909,6 @@
|
||||||
"core.openmodinbrowser": "Otevřít {{$a}} v prohlížeči",
|
"core.openmodinbrowser": "Otevřít {{$a}} v prohlížeči",
|
||||||
"core.othergroups": "Další skupiny",
|
"core.othergroups": "Další skupiny",
|
||||||
"core.pagea": "Stránka {{$a}}",
|
"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.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.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Telefon",
|
"core.phone": "Telefon",
|
||||||
|
|
|
@ -1752,7 +1752,6 @@
|
||||||
"core.openinbrowser": "Åben i browser",
|
"core.openinbrowser": "Åben i browser",
|
||||||
"core.othergroups": "Andre grupper",
|
"core.othergroups": "Andre grupper",
|
||||||
"core.pagea": "Side {{$a}}",
|
"core.pagea": "Side {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "Brug knappen forneden til at betale og blive tilmeldt umiddelbart derefter.",
|
"core.paymentinstant": "Brug knappen forneden til at betale og blive tilmeldt umiddelbart derefter.",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Telefon",
|
"core.phone": "Telefon",
|
||||||
|
|
|
@ -1841,7 +1841,6 @@
|
||||||
"core.openinbrowser": "Ανοίξτε στον περιηγητή.",
|
"core.openinbrowser": "Ανοίξτε στον περιηγητή.",
|
||||||
"core.othergroups": "Άλλες ομάδες",
|
"core.othergroups": "Άλλες ομάδες",
|
||||||
"core.pagea": "Σελίδα {{$a}}",
|
"core.pagea": "Σελίδα {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "Χρησιμοποιήστε το παρακάτω πλήκτρο για να πληρώσετε και να εγγραφείτε μέσα σε λίγα λεπτά!",
|
"core.paymentinstant": "Χρησιμοποιήστε το παρακάτω πλήκτρο για να πληρώσετε και να εγγραφείτε μέσα σε λίγα λεπτά!",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Τηλέφωνο",
|
"core.phone": "Τηλέφωνο",
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"core.listsep": ",",
|
"core.listsep": ",",
|
||||||
"core.login.loginsteps": "For full access to this site, you first need to create an account.",
|
"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.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.paymentinstant": "Use the button below to pay and be enrolled within minutes!",
|
||||||
"core.settings.license": "License",
|
"core.settings.license": "License",
|
||||||
"core.strftimedate": "%B %d, %Y",
|
"core.strftimedate": "%B %d, %Y",
|
||||||
|
|
|
@ -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.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.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.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.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.favourites": "Starred",
|
||||||
"core.filename": "Filename",
|
"core.filename": "Filename",
|
||||||
|
|
|
@ -1913,7 +1913,7 @@
|
||||||
"core.openmodinbrowser": "Abrir {{$a}} en navegador",
|
"core.openmodinbrowser": "Abrir {{$a}} en navegador",
|
||||||
"core.othergroups": "Otros grupos",
|
"core.othergroups": "Otros grupos",
|
||||||
"core.pagea": "Página {{$a}}",
|
"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.paymentinstant": "¡Utilice el botón de abajo para pagar y poder inscribirse en minutos!",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Teléfono",
|
"core.phone": "Teléfono",
|
||||||
|
|
|
@ -1913,7 +1913,6 @@
|
||||||
"core.openmodinbrowser": "Abrir {{$a}} en el navegador",
|
"core.openmodinbrowser": "Abrir {{$a}} en el navegador",
|
||||||
"core.othergroups": "Otros grupos",
|
"core.othergroups": "Otros grupos",
|
||||||
"core.pagea": "Página {{$a}}",
|
"core.pagea": "Página {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "¡Utilice el botón de abajo para pagar y poder matricularse en minutos!",
|
"core.paymentinstant": "¡Utilice el botón de abajo para pagar y poder matricularse en minutos!",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Teléfono",
|
"core.phone": "Teléfono",
|
||||||
|
|
|
@ -1875,7 +1875,6 @@
|
||||||
"core.openinbrowser": "Ouvrir dans le navigateur",
|
"core.openinbrowser": "Ouvrir dans le navigateur",
|
||||||
"core.othergroups": "Autres groupes",
|
"core.othergroups": "Autres groupes",
|
||||||
"core.pagea": "Page {{$a}}",
|
"core.pagea": "Page {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "Le bouton ci-dessous vous permet de payer et de vous inscrire en quelques minutes !",
|
"core.paymentinstant": "Le bouton ci-dessous vous permet de payer et de vous inscrire en quelques minutes !",
|
||||||
"core.percentagenumber": "{{$a}} %",
|
"core.percentagenumber": "{{$a}} %",
|
||||||
"core.phone": "Téléphone",
|
"core.phone": "Téléphone",
|
||||||
|
|
|
@ -1521,7 +1521,6 @@
|
||||||
"core.online": "Online",
|
"core.online": "Online",
|
||||||
"core.othergroups": "Egyéb csoportok",
|
"core.othergroups": "Egyéb csoportok",
|
||||||
"core.pagea": "{{$a}} oldal",
|
"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.paymentinstant": "A fizetéshez és a perceken belüli beiratkozáshoz használja az alábbi gombot!",
|
||||||
"core.phone": "Telefon",
|
"core.phone": "Telefon",
|
||||||
"core.pictureof": "Kép",
|
"core.pictureof": "Kép",
|
||||||
|
|
|
@ -1769,7 +1769,6 @@
|
||||||
"core.openinbrowser": "Apri nel browser",
|
"core.openinbrowser": "Apri nel browser",
|
||||||
"core.othergroups": "Altri gruppi",
|
"core.othergroups": "Altri gruppi",
|
||||||
"core.pagea": "Pagina {{$a}}",
|
"core.pagea": "Pagina {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "Utilizza il pulsante sottostante per pagare ed essere iscritto in pochi minuti!",
|
"core.paymentinstant": "Utilizza il pulsante sottostante per pagare ed essere iscritto in pochi minuti!",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Telefono",
|
"core.phone": "Telefono",
|
||||||
|
|
|
@ -1717,7 +1717,6 @@
|
||||||
"core.openinbrowser": "ブラウザで開く",
|
"core.openinbrowser": "ブラウザで開く",
|
||||||
"core.othergroups": "他のグループ",
|
"core.othergroups": "他のグループ",
|
||||||
"core.pagea": "ページ {{$a}}",
|
"core.pagea": "ページ {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "下のボタンをお使いください。支払いおよび登録がすぐに完了します!",
|
"core.paymentinstant": "下のボタンをお使いください。支払いおよび登録がすぐに完了します!",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "電話",
|
"core.phone": "電話",
|
||||||
|
|
|
@ -1385,7 +1385,6 @@
|
||||||
"core.openfullimage": "전체 크기 이미지를 보려면 여기를 클릭하십시오.",
|
"core.openfullimage": "전체 크기 이미지를 보려면 여기를 클릭하십시오.",
|
||||||
"core.openinbrowser": "브라우저에서 열기",
|
"core.openinbrowser": "브라우저에서 열기",
|
||||||
"core.pagea": "페이지 {{$a}}",
|
"core.pagea": "페이지 {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "신속하게 등록금 지불 및 등록을 마치려면 아래의 버튼을 사용하시오!",
|
"core.paymentinstant": "신속하게 등록금 지불 및 등록을 마치려면 아래의 버튼을 사용하시오!",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "전화",
|
"core.phone": "전화",
|
||||||
|
|
|
@ -1634,7 +1634,6 @@
|
||||||
"core.openinbrowser": "Atidaryti naršyklėje",
|
"core.openinbrowser": "Atidaryti naršyklėje",
|
||||||
"core.othergroups": "Kitos grupės",
|
"core.othergroups": "Kitos grupės",
|
||||||
"core.pagea": "{{$a}} puslapis",
|
"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.paymentinstant": "Naudokite toliau pateiktą mygtuką, kad sumokėtumėte ir būtumėte įregistruoti per kelias minutes.",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Telefonas",
|
"core.phone": "Telefonas",
|
||||||
|
|
|
@ -1576,7 +1576,6 @@
|
||||||
"core.online": "På nett",
|
"core.online": "På nett",
|
||||||
"core.othergroups": "Andre grupper",
|
"core.othergroups": "Andre grupper",
|
||||||
"core.pagea": "Side {{$a}}",
|
"core.pagea": "Side {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "Bruk knappen under for å betale og melde deg på kurset.",
|
"core.paymentinstant": "Bruk knappen under for å betale og melde deg på kurset.",
|
||||||
"core.phone": "Telefon",
|
"core.phone": "Telefon",
|
||||||
"core.pictureof": "Bilde av {{$a}}",
|
"core.pictureof": "Bilde av {{$a}}",
|
||||||
|
|
|
@ -1809,7 +1809,7 @@
|
||||||
"core.openinbrowser": "Abrir no navegador",
|
"core.openinbrowser": "Abrir no navegador",
|
||||||
"core.othergroups": "Outros grupos",
|
"core.othergroups": "Outros grupos",
|
||||||
"core.pagea": "Página {{$a}}",
|
"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.paymentinstant": "Clique o botão abaixo para efetuar o pagamento e fazer a sua inscrição em poucos minutos!",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Fone",
|
"core.phone": "Fone",
|
||||||
|
|
|
@ -1913,7 +1913,6 @@
|
||||||
"core.openmodinbrowser": "Abrir {{$a}} no navegador",
|
"core.openmodinbrowser": "Abrir {{$a}} no navegador",
|
||||||
"core.othergroups": "Outros grupos",
|
"core.othergroups": "Outros grupos",
|
||||||
"core.pagea": "Página {{$a}}",
|
"core.pagea": "Página {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "Use o botão abaixo para pagar e completar a inscrição!",
|
"core.paymentinstant": "Use o botão abaixo para pagar e completar a inscrição!",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Telefone",
|
"core.phone": "Telefone",
|
||||||
|
|
|
@ -1841,7 +1841,6 @@
|
||||||
"core.openinbrowser": "Odpri v brskalniku",
|
"core.openinbrowser": "Odpri v brskalniku",
|
||||||
"core.othergroups": "Ostale skupine",
|
"core.othergroups": "Ostale skupine",
|
||||||
"core.pagea": "Stran {{$a}}",
|
"core.pagea": "Stran {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "Uporabite spodnje gumbe za plačilo in vpis v nekaj minutah!",
|
"core.paymentinstant": "Uporabite spodnje gumbe za plačilo in vpis v nekaj minutah!",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Telefon",
|
"core.phone": "Telefon",
|
||||||
|
|
|
@ -1700,7 +1700,6 @@
|
||||||
"core.openinbrowser": "Отвори у веб читачу",
|
"core.openinbrowser": "Отвори у веб читачу",
|
||||||
"core.othergroups": "Друге групе",
|
"core.othergroups": "Друге групе",
|
||||||
"core.pagea": "Страница {{$a}}",
|
"core.pagea": "Страница {{$a}}",
|
||||||
"core.parentlanguage": "en",
|
|
||||||
"core.paymentinstant": "Употребите дугме испод како бисте извршили уплату и уписали курс у року од неколико минута!",
|
"core.paymentinstant": "Употребите дугме испод како бисте извршили уплату и уписали курс у року од неколико минута!",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Телефон",
|
"core.phone": "Телефон",
|
||||||
|
|
|
@ -1700,7 +1700,6 @@
|
||||||
"core.openinbrowser": "Otvori u veb čitaču",
|
"core.openinbrowser": "Otvori u veb čitaču",
|
||||||
"core.othergroups": "Druge grupe",
|
"core.othergroups": "Druge grupe",
|
||||||
"core.pagea": "Stranica {{$a}}",
|
"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.paymentinstant": "Upotrebite dugme ispod kako biste izvršili uplatu i upisali kurs u roku od nekoliko minuta!",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "Telefon",
|
"core.phone": "Telefon",
|
||||||
|
|
|
@ -1844,7 +1844,6 @@
|
||||||
"core.openinbrowser": "在浏览器中打开",
|
"core.openinbrowser": "在浏览器中打开",
|
||||||
"core.othergroups": "其他小组",
|
"core.othergroups": "其他小组",
|
||||||
"core.pagea": "页 {{$a}}",
|
"core.pagea": "页 {{$a}}",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "点击下面的按钮便可以快速付费并加入课程!",
|
"core.paymentinstant": "点击下面的按钮便可以快速付费并加入课程!",
|
||||||
"core.phone": "电话",
|
"core.phone": "电话",
|
||||||
"core.pictureof": "{{$a}}的头像",
|
"core.pictureof": "{{$a}}的头像",
|
||||||
|
|
|
@ -1636,7 +1636,6 @@
|
||||||
"core.openinbrowser": "以瀏覽器開啟",
|
"core.openinbrowser": "以瀏覽器開啟",
|
||||||
"core.othergroups": "其他群組",
|
"core.othergroups": "其他群組",
|
||||||
"core.pagea": "第 {{$a}} 頁",
|
"core.pagea": "第 {{$a}} 頁",
|
||||||
"core.parentlanguage": "",
|
|
||||||
"core.paymentinstant": "使用以下按鈕立即付款及註冊。",
|
"core.paymentinstant": "使用以下按鈕立即付款及註冊。",
|
||||||
"core.percentagenumber": "{{$a}}%",
|
"core.percentagenumber": "{{$a}}%",
|
||||||
"core.phone": "電話",
|
"core.phone": "電話",
|
||||||
|
|
|
@ -21,9 +21,9 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
/**
|
/**
|
||||||
* Component to show a loading spinner and message while data is being loaded.
|
* 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.
|
* 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' and 'dynMessage' attributes aren't set, default message "Loading" is shown.
|
* If 'message' isn't set, default message "Loading" is shown.
|
||||||
* 'message' attribute accepts hardcoded strings, variables, filters, etc. E.g. message="'core.loading' | translate".
|
* 'message' attribute accepts hardcoded strings, variables, filters, etc. E.g. [message]="'core.loading' | translate".
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* <core-loading [message]="loadingMessage" [hideUntil]="dataLoaded">
|
* <core-loading [message]="loadingMessage" [hideUntil]="dataLoaded">
|
||||||
|
@ -44,7 +44,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
animations: [coreShowHideAnimation]
|
animations: [coreShowHideAnimation]
|
||||||
})
|
})
|
||||||
export class CoreLoadingComponent implements OnInit, OnChanges {
|
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.
|
@Input() message?: string; // Message to show while loading.
|
||||||
@ViewChild('content') content: ElementRef;
|
@ViewChild('content') content: ElementRef;
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add class if loaded on init.
|
// Add class if loaded on init.
|
||||||
if (this.hideUntil) {
|
if (!!this.hideUntil) {
|
||||||
this.element.classList.add('core-loading-loaded');
|
this.element.classList.add('core-loading-loaded');
|
||||||
this.content.nativeElement.classList.add('core-loading-content');
|
this.content.nativeElement.classList.add('core-loading-content');
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||||
if (changes.hideUntil) {
|
if (changes.hideUntil) {
|
||||||
if (changes.hideUntil.currentValue === true) {
|
if (!!this.hideUntil) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Content is loaded so, center the spinner on the content itself.
|
// Content is loaded so, center the spinner on the content itself.
|
||||||
this.element.classList.add('core-loading-loaded');
|
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.
|
// Trigger the event after a timeout since the elements inside ngIf haven't been added to DOM yet.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.eventsProvider.trigger(CoreEventsProvider.CORE_LOADING_CHANGED, {
|
this.eventsProvider.trigger(CoreEventsProvider.CORE_LOADING_CHANGED, {
|
||||||
loaded: changes.hideUntil.currentValue,
|
loaded: !!this.hideUntil,
|
||||||
uniqueId: this.uniqueId
|
uniqueId: this.uniqueId
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
"app_id": "com.moodle.moodlemobile",
|
"app_id": "com.moodle.moodlemobile",
|
||||||
"appname": "Moodle Mobile",
|
"appname": "Moodle Mobile",
|
||||||
"desktopappname": "Moodle Desktop",
|
"desktopappname": "Moodle Desktop",
|
||||||
"versioncode": 3900,
|
"versioncode": 3910,
|
||||||
"versionname": "3.9.0",
|
"versionname": "3.9.1",
|
||||||
"cache_update_frequency_usually": 420000,
|
"cache_update_frequency_usually": 420000,
|
||||||
"cache_update_frequency_often": 1200000,
|
"cache_update_frequency_often": 1200000,
|
||||||
"cache_update_frequency_sometimes": 3600000,
|
"cache_update_frequency_sometimes": 3600000,
|
||||||
|
@ -81,6 +81,7 @@
|
||||||
"siteurl": "",
|
"siteurl": "",
|
||||||
"sitename": "",
|
"sitename": "",
|
||||||
"multisitesdisplay": "",
|
"multisitesdisplay": "",
|
||||||
|
"onlyallowlistedsites": false,
|
||||||
"skipssoconfirmation": false,
|
"skipssoconfirmation": false,
|
||||||
"forcedefaultlanguage": false,
|
"forcedefaultlanguage": false,
|
||||||
"privacypolicy": "https:\/\/moodle.net\/moodle-app-privacy\/",
|
"privacypolicy": "https:\/\/moodle.net\/moodle-app-privacy\/",
|
||||||
|
|
|
@ -105,4 +105,10 @@ ion-app.app-root page-core-login-site {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.core-login-site-qrcode-separator {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,16 @@
|
||||||
<div padding>
|
<div padding>
|
||||||
<button ion-button block [disabled]="siteChecked && !isBrowserSSO && !credForm.valid" class="core-login-login-button">{{ 'core.login.loginbutton' | translate }}</button>
|
<button ion-button block [disabled]="siteChecked && !isBrowserSSO && !credForm.valid" class="core-login-login-button">{{ 'core.login.loginbutton' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="showScanQR">
|
||||||
|
<div class="core-login-site-qrcode-separator">{{ 'core.login.or' | translate }}</div>
|
||||||
|
<ion-item class="core-login-site-qrcode" no-lines>
|
||||||
|
<a ion-button block color="light" margin-top icon-start text-wrap (click)="showInstructionsAndScanQR()">
|
||||||
|
<core-icon name="fa-qrcode" aria-hidden="true"></core-icon>
|
||||||
|
{{ 'core.scanqr' | translate }}
|
||||||
|
</a>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Forgotten password button. -->
|
<!-- Forgotten password button. -->
|
||||||
|
|
|
@ -16,12 +16,14 @@ import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreUtils } from '@providers/utils/utils';
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreLoginHelperProvider } from '../../providers/helper';
|
import { CoreLoginHelperProvider } from '../../providers/helper';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { CoreConfigConstants } from '../../../../configconstants';
|
import { CoreConfigConstants } from '../../../../configconstants';
|
||||||
|
import { CoreCustomURLSchemes } from '@providers/urlschemes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page to enter the user credentials.
|
* Page to enter the user credentials.
|
||||||
|
@ -47,6 +49,7 @@ export class CoreLoginCredentialsPage {
|
||||||
isBrowserSSO = false;
|
isBrowserSSO = false;
|
||||||
isFixedUrlSet = false;
|
isFixedUrlSet = false;
|
||||||
showForgottenPassword = true;
|
showForgottenPassword = true;
|
||||||
|
showScanQR: boolean;
|
||||||
|
|
||||||
protected siteConfig;
|
protected siteConfig;
|
||||||
protected eventThrown = false;
|
protected eventThrown = false;
|
||||||
|
@ -74,6 +77,17 @@ export class CoreLoginCredentialsPage {
|
||||||
username: [navParams.get('username') || '', Validators.required],
|
username: [navParams.get('username') || '', Validators.required],
|
||||||
password: ['', 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 {
|
signup(): void {
|
||||||
this.navCtrl.push('CoreLoginEmailSignupPage', { siteUrl: this.siteUrl });
|
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<void> {
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,16 +14,22 @@
|
||||||
<div text-center padding margin-bottom [class.hidden]="hasSites || enteredSiteUrl" class="core-login-site-logo">
|
<div text-center padding margin-bottom [class.hidden]="hasSites || enteredSiteUrl" class="core-login-site-logo">
|
||||||
<img src="assets/img/login_logo.png" class="avatar-full login-logo" role="presentation">
|
<img src="assets/img/login_logo.png" class="avatar-full login-logo" role="presentation">
|
||||||
</div>
|
</div>
|
||||||
<form ion-list [formGroup]="siteForm" (ngSubmit)="connect($event, siteForm.value.siteUrl)" *ngIf="!fixedSites || fixedDisplay == 'select'" #siteFormEl>
|
<form ion-list [formGroup]="siteForm" (ngSubmit)="connect($event, siteForm.value.siteUrl)" *ngIf="!fixedSites || siteSelector == 'select'" #siteFormEl>
|
||||||
<!-- Form to input the site URL if there are no fixed sites. -->
|
<!-- Form to input the site URL if there are no fixed sites. -->
|
||||||
<ng-container *ngIf="!fixedSites">
|
<ng-container *ngIf="!fixedSites">
|
||||||
|
<ng-container *ngIf="siteSelector == 'url'">
|
||||||
|
<ion-item>
|
||||||
|
<ion-label stacked><h2>{{ 'core.login.siteaddress' | translate }}</h2></ion-label>
|
||||||
|
<ion-input name="url" type="url" placeholder="https://campus.example.edu" formControlName="siteUrl" [core-auto-focus]="showKeyboard"></ion-input>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="siteSelector != 'url'">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label stacked><h2>{{ 'core.login.siteaddress' | translate }}</h2></ion-label>
|
<ion-label stacked><h2>{{ 'core.login.siteaddress' | translate }}</h2></ion-label>
|
||||||
<ion-input name="url" placeholder="https://campus.example.edu" formControlName="siteUrl" [core-auto-focus]="showKeyboard" (ionChange)="searchSite($event, siteForm.value.siteUrl)"></ion-input>
|
<ion-input name="url" placeholder="https://campus.example.edu" formControlName="siteUrl" [core-auto-focus]="showKeyboard" (ionChange)="searchSite($event, siteForm.value.siteUrl)"></ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ion-list *ngIf="!fixedSites" [class.hidden]="!hasSites && !enteredSiteUrl" class="core-login-site-list">
|
<ion-list [class.hidden]="!hasSites && !enteredSiteUrl" class="core-login-site-list">
|
||||||
<ion-item no-lines class="core-login-site-list-title"><h2 class="item-heading">{{ 'core.login.selectsite' | translate }}</h2></ion-item>
|
<ion-item no-lines class="core-login-site-list-title"><h2 class="item-heading">{{ 'core.login.selectsite' | translate }}</h2></ion-item>
|
||||||
<button ion-item *ngIf="enteredSiteUrl" (click)="connect($event, enteredSiteUrl.url)" [attr.aria-label]="'core.login.connect' | translate" detail-push class="core-login-entered-site">
|
<button ion-item *ngIf="enteredSiteUrl" (click)="connect($event, enteredSiteUrl.url)" [attr.aria-label]="'core.login.connect' | translate" detail-push class="core-login-entered-site">
|
||||||
<ion-thumbnail item-start>
|
<ion-thumbnail item-start>
|
||||||
|
@ -47,44 +53,51 @@
|
||||||
</div>
|
</div>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
<div *ngIf="!fixedSites && !hasSites && loadingSites" class="core-login-site-nolist-loading"><ion-spinner></ion-spinner></div>
|
<div *ngIf="!hasSites && loadingSites" class="core-login-site-nolist-loading"><ion-spinner></ion-spinner></div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<!-- Pick the site from a list of fixed sites. -->
|
<!-- Pick the site from a list of fixed sites. -->
|
||||||
<ion-item *ngIf="fixedSites && fixedDisplay == 'select'" margin-vertical text-wrap>
|
<ion-item *ngIf="fixedSites && siteSelector == 'select'" margin-vertical text-wrap>
|
||||||
<ion-label stacked for="siteSelect">{{ 'core.login.selectsite' | translate }}</ion-label>
|
<ion-label stacked for="siteSelect">{{ 'core.login.selectsite' | translate }}</ion-label>
|
||||||
<ion-select formControlName="siteUrl" name="url" placeholder="{{ 'core.login.siteaddress' | translate }}" interface="action-sheet">
|
<ion-select formControlName="siteUrl" name="url" placeholder="{{ 'core.login.siteaddress' | translate }}" interface="action-sheet">
|
||||||
<ion-option *ngFor="let site of fixedSites" [value]="site.url">{{site.name}}</ion-option>
|
<ion-option *ngFor="let site of fixedSites" [value]="site.url">{{site.name}}</ion-option>
|
||||||
</ion-select>
|
</ion-select>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ng-container *ngIf="!fixedSites && showScanQR && !hasSites && !enteredSiteUrl">
|
<ion-item *ngIf="(fixedSites && siteSelector == 'select') || (!fixedSites && siteSelector == 'url')" no-lines>
|
||||||
|
<button ion-button block [disabled]="!siteForm.valid" text-wrap>{{ 'core.login.connect' | translate }}</button>
|
||||||
|
</ion-item>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ng-container *ngIf="fixedSites">
|
||||||
|
<!-- Pick the site from a list of fixed sites. -->
|
||||||
|
<ion-list *ngIf="siteSelector == 'list' || siteSelector == 'listnourl'">
|
||||||
|
<ion-item no-lines><h2 class="item-heading">{{ 'core.login.selectsite' | translate }}</h2></ion-item>
|
||||||
|
<ion-searchbar *ngIf="fixedSites.length > 4" [(ngModel)]="filter" (ionInput)="filterChanged($event)" (ionCancel)="filterChanged()" [placeholder]="'core.login.findyoursite' | translate"></ion-searchbar>
|
||||||
|
<ion-item *ngFor="let site of filteredSites" (click)="connect($event, site.url)" [title]="site.name" detail-push text-wrap>
|
||||||
|
<h2>{{site.name}}</h2>
|
||||||
|
<p *ngIf="siteSelector == 'list'">{{site.url}}</p>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<!-- Display them using buttons. -->
|
||||||
|
<div *ngIf="siteSelector == 'buttons'">
|
||||||
|
<p class="padding no-padding-bottom">{{ 'core.login.selectsite' | translate }}</p>
|
||||||
|
<a *ngFor="let site of fixedSites" ion-button block (click)="connect($event, site.url)" [title]="site.name" margin-bottom>{{site.name}}</a>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="showScanQR && !hasSites && !enteredSiteUrl">
|
||||||
<div class="core-login-site-qrcode-separator">{{ 'core.login.or' | translate }}</div>
|
<div class="core-login-site-qrcode-separator">{{ 'core.login.or' | translate }}</div>
|
||||||
<ion-item class="core-login-site-qrcode">
|
<ion-item class="core-login-site-qrcode" no-lines>
|
||||||
<a ion-button block color="light" margin-top icon-start (click)="showInstructionsAndScanQR()">
|
<a ion-button block color="light" margin-top icon-start (click)="showInstructionsAndScanQR()" text-wrap>
|
||||||
<core-icon name="fa-qrcode" aria-hidden="true"></core-icon>
|
<core-icon name="fa-qrcode" aria-hidden="true"></core-icon>
|
||||||
{{ 'core.scanqr' | translate }}
|
{{ 'core.scanqr' | translate }}
|
||||||
</a>
|
</a>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Pick the site from a list of fixed sites. -->
|
|
||||||
<ion-list *ngIf="fixedSites && (fixedDisplay == 'list' || fixedDisplay == 'listnourl')">
|
|
||||||
<ion-item no-lines><h2 class="item-heading">{{ 'core.login.selectsite' | translate }}</h2></ion-item>
|
|
||||||
<ion-searchbar *ngIf="fixedSites.length > 4" [(ngModel)]="filter" (ionInput)="filterChanged($event)" (ionCancel)="filterChanged()" [placeholder]="'core.login.findyoursite' | translate"></ion-searchbar>
|
|
||||||
<ion-item *ngFor="let site of filteredSites" (click)="connect($event, site.url)" [title]="site.name" detail-push text-wrap>
|
|
||||||
<h2>{{site.name}}</h2>
|
|
||||||
<p *ngIf="fixedDisplay == 'list'">{{site.url}}</p>
|
|
||||||
</ion-item>
|
|
||||||
</ion-list>
|
|
||||||
|
|
||||||
<!-- Display them using buttons. -->
|
|
||||||
<div *ngIf="fixedSites && fixedDisplay == 'buttons'">
|
|
||||||
<p class="padding no-padding-bottom">{{ 'core.login.selectsite' | translate }}</p>
|
|
||||||
<a *ngFor="let site of fixedSites" ion-button block (click)="connect($event, site.url)" [title]="site.name" margin-bottom>{{site.name}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Help. -->
|
<!-- Help. -->
|
||||||
<ion-list no-lines margin-top>
|
<ion-list no-lines margin-top>
|
||||||
<a ion-item text-center text-wrap class="core-login-need-help" (click)="showHelp()" detail-none>
|
<a ion-item text-center text-wrap class="core-login-need-help" (click)="showHelp()" detail-none>
|
||||||
|
|
|
@ -129,10 +129,4 @@ ion-app.app-root page-core-login-site {
|
||||||
.core-login-default-icon {
|
.core-login-default-icon {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-login-site-qrcode-separator {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 12px;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ export class CoreLoginSitePage {
|
||||||
siteForm: FormGroup;
|
siteForm: FormGroup;
|
||||||
fixedSites: CoreLoginSiteInfo[];
|
fixedSites: CoreLoginSiteInfo[];
|
||||||
filteredSites: CoreLoginSiteInfo[];
|
filteredSites: CoreLoginSiteInfo[];
|
||||||
fixedDisplay = 'buttons';
|
siteSelector = 'sitefinder';
|
||||||
showKeyboard = false;
|
showKeyboard = false;
|
||||||
filter = '';
|
filter = '';
|
||||||
sites: CoreLoginSiteInfoExtended[] = [];
|
sites: CoreLoginSiteInfoExtended[] = [];
|
||||||
|
@ -80,17 +80,16 @@ export class CoreLoginSitePage {
|
||||||
protected textUtils: CoreTextUtilsProvider) {
|
protected textUtils: CoreTextUtilsProvider) {
|
||||||
|
|
||||||
this.showKeyboard = !!navParams.get('showKeyboard');
|
this.showKeyboard = !!navParams.get('showKeyboard');
|
||||||
this.showScanQR = this.utils.canScanQR();
|
|
||||||
|
|
||||||
let url = '';
|
let url = '';
|
||||||
|
this.siteSelector = CoreConfigConstants.multisitesdisplay;
|
||||||
|
|
||||||
// Load fixed sites if they're set.
|
// Load fixed sites if they're set.
|
||||||
if (this.loginHelper.hasSeveralFixedSites()) {
|
if (this.loginHelper.hasSeveralFixedSites()) {
|
||||||
this.fixedSites = <any[]> this.loginHelper.getFixedSites();
|
this.fixedSites = <any[]> this.loginHelper.getFixedSites();
|
||||||
this.fixedDisplay = CoreConfigConstants.multisitesdisplay;
|
|
||||||
// Autoselect if not defined.
|
// Autoselect if not defined.
|
||||||
if (['list', 'listnourl', 'select', 'buttons'].indexOf(this.fixedDisplay) < 0) {
|
if (['list', 'listnourl', 'select', 'buttons'].indexOf(this.siteSelector) < 0) {
|
||||||
this.fixedDisplay = this.fixedSites.length > 8 ? 'list' : (this.fixedSites.length > 3 ? 'select' : 'buttons');
|
this.siteSelector = this.fixedSites.length > 8 ? 'list' : (this.fixedSites.length > 3 ? 'select' : 'buttons');
|
||||||
}
|
}
|
||||||
this.filteredSites = this.fixedSites;
|
this.filteredSites = this.fixedSites;
|
||||||
url = this.fixedSites[0].url;
|
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({
|
this.siteForm = fb.group({
|
||||||
siteUrl: [url, this.moodleUrlValidator()]
|
siteUrl: [url, this.moodleUrlValidator()]
|
||||||
});
|
});
|
||||||
|
|
|
@ -67,7 +67,8 @@ export class CoreMainMenuMorePage implements OnDestroy {
|
||||||
this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.loadSiteInfo.bind(this),
|
this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.loadSiteInfo.bind(this),
|
||||||
sitesProvider.getCurrentSiteId());
|
sitesProvider.getCurrentSiteId());
|
||||||
this.loadSiteInfo();
|
this.loadSiteInfo();
|
||||||
this.showScanQR = this.utils.canScanQR();
|
this.showScanQR = this.utils.canScanQR() &&
|
||||||
|
!this.sitesProvider.getCurrentSite().isFeatureDisabled('CoreMainMenuDelegate_QrReader');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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.",
|
"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.",
|
"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.",
|
"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.",
|
"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",
|
"favourites": "Starred",
|
||||||
"filename": "Filename",
|
"filename": "Filename",
|
||||||
|
|
|
@ -55,7 +55,9 @@ export class CoreLangProvider {
|
||||||
|
|
||||||
translate.onLangChange.subscribe((event: any) => {
|
translate.onLangChange.subscribe((event: any) => {
|
||||||
platform.setLang(event.lang, true);
|
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins
|
||||||
import { CoreConfigConstants } from '../configconstants';
|
import { CoreConfigConstants } from '../configconstants';
|
||||||
import { CoreConstants } from '@core/constants';
|
import { CoreConstants } from '@core/constants';
|
||||||
import { makeSingleton } from '@singletons/core.singletons';
|
import { makeSingleton } from '@singletons/core.singletons';
|
||||||
|
import { CoreUrl } from '@singletons/url';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All params that can be in a custom URL scheme.
|
* All params that can be in a custom URL scheme.
|
||||||
|
@ -166,6 +167,12 @@ export class CoreCustomURLSchemesProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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) {
|
if (data.redirect && data.redirect.match(/^https?:\/\//) && data.redirect.indexOf(data.siteUrl) == -1) {
|
||||||
// Redirect URL must belong to the same site. Reject.
|
// Redirect URL must belong to the same site. Reject.
|
||||||
throw this.translate.instant('core.contentlinks.errorredirectothersite');
|
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'));
|
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<boolean> {
|
||||||
|
if (this.loginHelper.isFixedUrlSet()) {
|
||||||
|
|
||||||
|
return CoreUrl.sameDomainAndPath(siteUrl, <string> this.loginHelper.getFixedSites());
|
||||||
|
} else if (this.loginHelper.hasSeveralFixedSites()) {
|
||||||
|
const sites = <any[]> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -162,7 +162,7 @@ export class CoreWSProvider {
|
||||||
this.logger = logger.getInstance('CoreWSProvider');
|
this.logger = logger.getInstance('CoreWSProvider');
|
||||||
|
|
||||||
platform.ready().then(() => {
|
platform.ready().then(() => {
|
||||||
if (this.appProvider.isMobile()) {
|
if (this.appProvider.isIOS()) {
|
||||||
(<any> cordova).plugin.http.setHeader('User-Agent', navigator.userAgent);
|
(<any> cordova).plugin.http.setHeader('User-Agent', navigator.userAgent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CoreTextUtils } from '@providers/utils/text';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parts contained within a url.
|
* Parts contained within a url.
|
||||||
*/
|
*/
|
||||||
|
@ -172,4 +174,27 @@ export class CoreUrl {
|
||||||
static removeProtocol(url: string): string {
|
static removeProtocol(url: string): string {
|
||||||
return url.replace(/^[a-zA-Z]+:\/\//i, '');
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue