diff --git a/local_moodleappbehat/tests/behat/behat_app.php b/local_moodleappbehat/tests/behat/behat_app.php
index 012aff300..a8e5bbc33 100644
--- a/local_moodleappbehat/tests/behat/behat_app.php
+++ b/local_moodleappbehat/tests/behat/behat_app.php
@@ -604,6 +604,7 @@ class behat_app extends behat_app_helper {
$data = $data->getColumnsHash()[0];
$title = array_keys($data)[0];
$data = (object) $data;
+ $username = $data->user ?? '';
switch ($title) {
case 'discussion':
@@ -645,7 +646,7 @@ class behat_app extends behat_app_helper {
throw new DriverException('Invalid custom link title - ' . $title);
}
- $this->open_moodleapp_custom_url($pageurl);
+ $this->open_moodleapp_custom_url($pageurl, '', $username);
}
/**
diff --git a/local_moodleappbehat/tests/behat/behat_app_helper.php b/local_moodleappbehat/tests/behat/behat_app_helper.php
index 4621b6a03..d255237d2 100644
--- a/local_moodleappbehat/tests/behat/behat_app_helper.php
+++ b/local_moodleappbehat/tests/behat/behat_app_helper.php
@@ -366,12 +366,15 @@ class behat_app_helper extends behat_base {
*
* @param string $script
* @param bool $blocking
+ * @param string $texttofind If set, when this text is found the operation is considered finished. This is useful for
+ * operations that might expect user input before finishing, like a confirm modal.
* @return mixed Result.
*/
- protected function zone_js(string $script, bool $blocking = false) {
+ protected function zone_js(string $script, bool $blocking = false, string $texttofind = '') {
$blockingjson = json_encode($blocking);
+ $locatortofind = !empty($texttofind) ? json_encode((object) ['text' => $texttofind]) : null;
- return $this->runtime_js("runInZone(() => window.behat.$script, $blockingjson)");
+ return $this->runtime_js("runInZone(() => window.behat.$script, $blockingjson, $locatortofind)");
}
/**
@@ -411,16 +414,14 @@ class behat_app_helper extends behat_base {
$privatetoken = $usertoken->privatetoken;
}
- // Generate custom URL.
- $parsed_url = parse_url($CFG->behat_wwwroot);
- $site = $parsed_url['host'] ?? '';
- $site .= isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
- $site .= $parsed_url['path'] ?? '';
- $url = $this->get_mobile_url_scheme() . "://$username@$site?token=$token&privatetoken=$privatetoken";
+ $url = $this->generate_custom_url([
+ 'username' => $username,
+ 'token' => $token,
+ 'privatetoken' => $privatetoken,
+ 'redirect' => $path,
+ ]);
- if (!empty($path)) {
- $url .= '&redirect='.urlencode($CFG->behat_wwwroot.$path);
- } else {
+ if (empty($path)) {
$successXPath = '//page-core-mainmenu';
}
@@ -434,14 +435,54 @@ class behat_app_helper extends behat_base {
*
* @param string $path To navigate.
* @param string $successXPath The XPath of the element to lookat after navigation.
+ * @param string $username The username to use.
*/
- protected function open_moodleapp_custom_url(string $path, string $successXPath = '') {
+ protected function open_moodleapp_custom_url(string $path, string $successXPath = '', string $username = '') {
global $CFG;
- $urlscheme = $this->get_mobile_url_scheme();
- $url = "$urlscheme://link=" . urlencode($CFG->behat_wwwroot.$path);
+ $url = $this->generate_custom_url([
+ 'username' => $username,
+ 'redirect' => $path,
+ ]);
- $this->handle_url($url);
+ $this->handle_url($url, $successXPath, $username ? 'This link belongs to another site' : '');
+ }
+
+ /**
+ * Generates a custom URL to be treated by the app.
+ *
+ * @param array $data Data to generate the URL.
+ */
+ protected function generate_custom_url(array $data): string {
+ global $CFG;
+
+ $parsed_url = parse_url($CFG->behat_wwwroot);
+ $parameters = [];
+
+ $url = $this->get_mobile_url_scheme() . '://' . ($parsed_url['scheme'] ?? 'http') . '://';
+ if (!empty($data['username'])) {
+ $url .= $data['username'] . '@';
+ }
+ $url .= $parsed_url['host'] ?? '';
+ $url .= isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
+ $url .= $parsed_url['path'] ?? '';
+
+ if (!empty($data['token'])) {
+ $parameters[] = 'token=' . $data['token'];
+ if (!empty($data['privatetoken'])) {
+ $parameters[] = 'privatetoken=' . $data['privatetoken'];
+ }
+ }
+
+ if (!empty($data['redirect'])) {
+ $parameters[] = 'redirect=' . urlencode($data['redirect']);
+ }
+
+ if (!empty($parameters)) {
+ $url .= '?' . implode('&', $parameters);
+ }
+
+ return $url;
}
/**
@@ -449,9 +490,11 @@ class behat_app_helper extends behat_base {
*
* @param string $customurl To navigate.
* @param string $successXPath The XPath of the element to lookat after navigation.
+ * @param string $texttofind If set, when this text is found the operation is considered finished. This is useful for
+ * operations that might expect user input before finishing, like a confirm modal.
*/
- protected function handle_url(string $customurl, string $successXPath = '') {
- $result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')");
+ protected function handle_url(string $customurl, string $successXPath = '', string $texttofind = '') {
+ $result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')", false, $texttofind);
if ($result !== 'OK') {
throw new DriverException('Error handling url - ' . $customurl . ' - '.$result);
diff --git a/src/addons/mod/assign/tests/behat/basic_usage.feature b/src/addons/mod/assign/tests/behat/basic_usage.feature
index 9cbf92c17..9adc224cb 100755
--- a/src/addons/mod/assign/tests/behat/basic_usage.feature
+++ b/src/addons/mod/assign/tests/behat/basic_usage.feature
@@ -132,7 +132,8 @@ Feature: Test basic usage of assignment activity in app
Then I should find "No attempt" in the app
Scenario: Add submission offline (online text) & Submit for grading offline & Sync submissions
- Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "assignment1" in the app
When I press "Add submission" in the app
And I switch network connection to offline
And I set the field "Online text submissions" to "Submission test" in the app
@@ -150,7 +151,8 @@ Feature: Test basic usage of assignment activity in app
But I should not find "This Assignment has offline data to be synchronised." in the app
Scenario: Edit an offline submission before synchronising it
- Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "assignment1" in the app
When I press "Add submission" in the app
And I switch network connection to offline
And I set the field "Online text submissions" to "Submission test original offline" in the app
@@ -178,8 +180,9 @@ Feature: Test basic usage of assignment activity in app
@lms_from4.5
Scenario: Remove submission offline and syncrhonize it
- Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app
- And I press "Add submission" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "assignment1" in the app
+ When I press "Add submission" in the app
And I set the field "Online text submissions" to "Submission test" in the app
And I press "Save" in the app
Then I should find "Draft (not submitted)" in the app
@@ -223,7 +226,8 @@ Feature: Test basic usage of assignment activity in app
@lms_from4.5
Scenario: Add submission offline after removing a submission offline
- Given I entered the assign activity "assignment1" on course "Course 1" as "student1" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "assignment1" in the app
When I press "Add submission" in the app
And I set the field "Online text submissions" to "Submission test online" in the app
And I press "Save" in the app
diff --git a/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature b/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature
index 739cada78..f7260640f 100755
--- a/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature
+++ b/src/addons/mod/bigbluebuttonbn/tests/behat/basic_usage.feature
@@ -25,7 +25,8 @@ Feature: Test basic usage of BBB activity in app
| bigbluebuttonbn | BBB 1 | Test BBB description | C1 | bbb1 | 0 | ## 1 January 2050 00:00 ## | 0 |
| bigbluebuttonbn | BBB 2 | Test BBB description | C1 | bbb2 | 0 | 0 | ## 1 January 2000 00:00 ## |
| bigbluebuttonbn | BBB 3 | Test BBB description | C1 | bbb3 | 0 | ## 1 January 2000 00:00 ## | ## 1 January 2050 00:00 ## |
- And I entered the bigbluebuttonbn activity "BBB 1" on course "Course 1" as "student1" in the app
+ And I entered the course "Course 1" as "student1" in the app
+ And I press "BBB 1" in the app
Then I should find "The session has not started yet." in the app
And I should find "Saturday, 1 January 2050, 12:00 AM" within "Open" "ion-item" in the app
@@ -107,7 +108,8 @@ Feature: Test basic usage of BBB activity in app
| bigbluebuttonbn | Room & recordings | C1 | bbb1 | 0 |
| bigbluebuttonbn | Room only | C1 | bbb2 | 1 |
| bigbluebuttonbn | Recordings only | C1 | bbb3 | 2 |
- And I entered the bigbluebuttonbn activity "Room & recordings" on course "Course 1" as "student1" in the app
+ And I entered the course "Course 1" as "student1" in the app
+ And I press "Room & recordings" in the app
Then I should find "This room is ready. You can join the session now." in the app
And I should be able to press "Join session" in the app
And I should find "Recordings" in the app
diff --git a/src/addons/mod/choice/tests/behat/basic_usage.feature b/src/addons/mod/choice/tests/behat/basic_usage.feature
index 5d087bfdf..fb44f8a54 100755
--- a/src/addons/mod/choice/tests/behat/basic_usage.feature
+++ b/src/addons/mod/choice/tests/behat/basic_usage.feature
@@ -21,7 +21,8 @@ Feature: Test basic usage of choice activity in app
Given the following "activities" exist:
| activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults |
| choice | Test single choice name | Test single choice description | C1 | choice1 | Option 1, Option 2, Option 3 | 0 | 0 | 1 |
- And I entered the choice activity "Test single choice name" on course "Course 1" as "student1" in the app
+ And I entered the course "Course 1" as "student1" in the app
+ And I press "Test single choice name" in the app
When I select "Option 1" in the app
And I select "Option 2" in the app
And I press "Save my choice" in the app
@@ -74,7 +75,8 @@ Feature: Test basic usage of choice activity in app
Given the following "activities" exist:
| activity | name | intro | course | idnumber | option | allowmultiple | allowupdate | showresults |
| choice | Test single choice name | Test single choice description | C1 | choice1 | Option 1, Option 2, Option 3 | 0 | 0 | 1 |
- And I entered the choice activity "Test single choice name" on course "Course 1" as "student1" in the app
+ And I entered the course "Course 1" as "student1" in the app
+ And I press "Test single choice name" in the app
When I select "Option 1" in the app
And I switch network connection to offline
And I select "Option 2" in the app
diff --git a/src/addons/mod/data/tests/behat/sync.feature b/src/addons/mod/data/tests/behat/sync.feature
index 50dccc49a..94d2e5a9c 100644
--- a/src/addons/mod/data/tests/behat/sync.feature
+++ b/src/addons/mod/data/tests/behat/sync.feature
@@ -28,9 +28,11 @@ Feature: Users can store entries in database activities when offline and sync wh
| data1 | text | Description | Link description |
Scenario: Create entry (offline)
- Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "Web links" in the app
And I switch network connection to offline
- And I should find "No entries yet" in the app
+ Then I should find "No entries yet" in the app
+
When I press "Add entry" in the app
And I set the following fields to these values in the app:
| URL | https://moodle.org/ |
@@ -39,29 +41,33 @@ Feature: Users can store entries in database activities when offline and sync wh
Then I should find "https://moodle.org/" in the app
And I should find "Moodle community site" in the app
And I should find "This Database has offline data to be synchronised" in the app
- And I go back in the app
+
+ When I go back in the app
And I switch network connection to wifi
And I press "Web links" near "General" in the app
- And I should find "https://moodle.org/" in the app
+ Then I should find "https://moodle.org/" in the app
And I should find "Moodle community site" in the app
And I should not find "This Database has offline data to be synchronised" in the app
Scenario: Update entry (offline) & Delete entry (offline)
- Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app
- And I should find "No entries yet" in the app
- And I press "Add entry" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "Web links" in the app
+ Then I should find "No entries yet" in the app
+
+ When I press "Add entry" in the app
And I set the following fields to these values in the app:
| URL | https://moodle.org/ |
| Description | Moodle community site |
And I press "Save" near "Web links" in the app
- And I should find "https://moodle.org/" in the app
+ Then I should find "https://moodle.org/" in the app
And I should find "Moodle community site" in the app
- And I press "Information" in the app
+
+ When I press "Information" in the app
And I press "Download" in the app
And I wait until the page is ready
And I close the popup in the app
And I switch network connection to offline
- When I press "Actions menu" in the app
+ And I press "Actions menu" in the app
And I press "Edit" in the app
And I set the following fields to these values in the app:
| URL | https://moodlecloud.com/ |
@@ -72,55 +78,64 @@ Feature: Users can store entries in database activities when offline and sync wh
And I should find "https://moodlecloud.com/" in the app
And I should find "Moodle Cloud" in the app
And I should find "This Database has offline data to be synchronised" in the app
- And I go back in the app
+
+ When I go back in the app
And I switch network connection to wifi
And I press "Web links" near "General" in the app
- And I should not find "https://moodle.org/" in the app
+ Then I should not find "https://moodle.org/" in the app
And I should not find "Moodle community site" in the app
And I should find "https://moodlecloud.com/" in the app
And I should find "Moodle Cloud" in the app
And I should not find "This Database has offline data to be synchronised" in the app
- And I press "Information" in the app
+
+ When I press "Information" in the app
And I press "Refresh" in the app
And I wait until the page is ready
And I switch network connection to offline
And I press "Actions menu" in the app
And I press "Delete" in the app
- And I should find "Are you sure you want to delete this entry?" in the app
- And I press "Delete" in the app
- And I should find "https://moodlecloud.com/" in the app
+ Then I should find "Are you sure you want to delete this entry?" in the app
+
+ When I press "Delete" in the app
+ Then I should find "https://moodlecloud.com/" in the app
And I should find "Moodle Cloud" in the app
And I should find "This Database has offline data to be synchronised" in the app
- And I go back in the app
+
+ When I go back in the app
And I switch network connection to wifi
And I press "Web links" near "General" in the app
- And I should not find "https://moodlecloud.com/" in the app
+ Then I should not find "https://moodlecloud.com/" in the app
And I should not find "Moodle Cloud" in the app
And I should not find "This Database has offline data to be synchronised" in the app
Scenario: Students can undo deleting entries to a database in the app while offline
- Given I entered the data activity "Web links" on course "Course 1" as "student1" in the app
- And I should find "No entries yet" in the app
- And I press "Add entry" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "Web links" in the app
+ Then I should find "No entries yet" in the app
+
+ When I press "Add entry" in the app
And I set the following fields to these values in the app:
| URL | https://moodle.org/ |
| Description | Moodle community site |
And I press "Save" near "Web links" in the app
- And I should find "https://moodle.org/" in the app
+ Then I should find "https://moodle.org/" in the app
And I should find "Moodle community site" in the app
- And I press "Information" in the app
+
+ When I press "Information" in the app
And I press "Download" in the app
And I wait until the page is ready
And I close the popup in the app
- When I switch network connection to offline
+ And I switch network connection to offline
And I press "Actions menu" in the app
And I press "Delete" in the app
- And I should find "Are you sure you want to delete this entry?" in the app
- And I press "Delete" in the app
- And I should find "https://moodle.org/" in the app
+ Then I should find "Are you sure you want to delete this entry?" in the app
+
+ When I press "Delete" in the app
+ Then I should find "https://moodle.org/" in the app
And I should find "Moodle community site" in the app
And I should find "This Database has offline data to be synchronised" in the app
- And I press "Actions menu" in the app
+
+ When I press "Actions menu" in the app
And I press "Restore" in the app
And I go back in the app
And I switch network connection to wifi
diff --git a/src/addons/mod/forum/tests/behat/basic_usage.feature b/src/addons/mod/forum/tests/behat/basic_usage.feature
index e0aebd9be..61dffc7b6 100755
--- a/src/addons/mod/forum/tests/behat/basic_usage.feature
+++ b/src/addons/mod/forum/tests/behat/basic_usage.feature
@@ -276,7 +276,8 @@ Feature: Test basic usage of forum activity in app
But I should not find "Not sent" in the app
Scenario: New discussion offline & Sync Forum
- Given I entered the forum activity "Test forum name" on course "Course 1" as "student1" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "Test forum name" in the app
When I switch network connection to offline
And I press "Add discussion topic" in the app
And I set the following fields to these values in the app:
diff --git a/src/addons/mod/forum/tests/behat/groups.feature b/src/addons/mod/forum/tests/behat/groups.feature
index 8c7aba79e..3cbdc09ef 100755
--- a/src/addons/mod/forum/tests/behat/groups.feature
+++ b/src/addons/mod/forum/tests/behat/groups.feature
@@ -35,7 +35,8 @@ Feature: Test usage of forum activity with groups in app
| forum2 | Disc vis ALL | Disc vis ALL | Disc vis ALL content | All participants |
Scenario: Student can only see the right groups
- Given I entered the forum activity "Separate groups forum" on course "Course 1" as "student1" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "Separate groups forum" in the app
Then I should find "Disc sep G1" in the app
And I should find "Disc sep ALL" in the app
But I should not find "Disc sep G2" in the app
@@ -65,7 +66,8 @@ Feature: Test usage of forum activity with groups in app
But I should not find "Disc vis G2" in the app
Scenario: Teacher can see all groups
- Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app
+ Given I entered the course "Course 1" as "teacher1" in the app
+ And I press "Separate groups forum" in the app
When I press "Separate groups" in the app
Then I should find "All participants" in the app
And I should find "Group 1" in the app
@@ -107,7 +109,8 @@ Feature: Test usage of forum activity with groups in app
But I should not find "Disc vis G2" in the app
Scenario: Student can only add discussions in his groups
- Given I entered the forum activity "Separate groups forum" on course "Course 1" as "student1" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "Separate groups forum" in the app
When I press "Add discussion topic" in the app
And I press "Advanced" in the app
Then I should not find "Post a copy to all groups" in the app
@@ -165,8 +168,9 @@ Feature: Test usage of forum activity with groups in app
Then I should find "My happy subject" in the app
Scenario: Teacher can add discussion to any group
- Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app
- And I press "Separate groups" in the app
+ Given I entered the course "Course 1" as "teacher1" in the app
+ And I press "Separate groups forum" in the app
+ When I press "Separate groups" in the app
And I press "All participants" in the app
And I press "Add discussion topic" in the app
And I press "Advanced" in the app
@@ -279,8 +283,9 @@ Feature: Test usage of forum activity with groups in app
Then I should find "My third subject" in the app
Scenario: Teacher can post a copy in all groups
- Given I entered the forum activity "Separate groups forum" on course "Course 1" as "teacher1" in the app
- And I press "Separate groups" in the app
+ Given I entered the course "Course 1" as "teacher1" in the app
+ And I press "Separate groups forum" in the app
+ When I press "Separate groups" in the app
And I press "Group 1" in the app
And I press "Add discussion topic" in the app
And I press "Advanced" in the app
diff --git a/src/addons/mod/glossary/tests/behat/basic_usage.feature b/src/addons/mod/glossary/tests/behat/basic_usage.feature
index b11156c46..c3ccd71ad 100644
--- a/src/addons/mod/glossary/tests/behat/basic_usage.feature
+++ b/src/addons/mod/glossary/tests/behat/basic_usage.feature
@@ -52,7 +52,8 @@ Feature: Test basic usage of glossary in app
Scenario: Navigate to glossary terms by link (auto-linking)
Given the "glossary" filter is "on"
- And I entered the glossary activity "Test glossary" on course "Course 1" as "student1" in the app
+ And I entered the course "Course 1" as "student1" in the app
+ And I press "Test glossary" in the app
Then the header should be "Test glossary" in the app
And I should find "Eggplant" in the app
And I should find "Cucumber" in the app
diff --git a/src/addons/mod/quiz/tests/behat/can_review.feature b/src/addons/mod/quiz/tests/behat/can_review.feature
index 870be3bb2..364d1dfeb 100644
--- a/src/addons/mod/quiz/tests/behat/can_review.feature
+++ b/src/addons/mod/quiz/tests/behat/can_review.feature
@@ -49,8 +49,9 @@ Feature: Users can only review attempts that are allowed to be reviewed
| 1 | True |
Scenario: Can review only when the attempt is allowed to be reviewed
- Given I entered the quiz activity "Quiz review after immed" on course "Course 1" as "student1" in the app
- And I press "Attempt 1" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "Quiz review after immed" in the app
+ When I press "Attempt 1" in the app
Then I should not be able to press "Review" in the app
When I go back in the app
diff --git a/src/addons/mod/survey/tests/behat/basic_usage.feature b/src/addons/mod/survey/tests/behat/basic_usage.feature
index 894f90f37..5ee4ed3e6 100755
--- a/src/addons/mod/survey/tests/behat/basic_usage.feature
+++ b/src/addons/mod/survey/tests/behat/basic_usage.feature
@@ -238,8 +238,9 @@ Feature: Test basic usage of survey activity in app
Given the following "activities" exist:
| activity | name | intro | template | course | idnumber | groupmode |
| survey | Test survey critical incidents | Test survey1 | 5 | C1 | survey1 | 0 |
- Given I entered the survey activity "Test survey critical incidents" on course "Course 1" as "student1" in the app
- And I switch network connection to offline
+ And I entered the course "Course 1" as "student1" in the app
+ And I press "Test survey critical incidents" in the app
+ When I switch network connection to offline
And I press "Submit" in the app
And I press "OK" in the app
Then I should see "This Survey has offline data to be synchronised."
diff --git a/src/addons/mod/workshop/tests/behat/basic_usage.feature b/src/addons/mod/workshop/tests/behat/basic_usage.feature
index 06441f66d..1199f166f 100644
--- a/src/addons/mod/workshop/tests/behat/basic_usage.feature
+++ b/src/addons/mod/workshop/tests/behat/basic_usage.feature
@@ -130,7 +130,8 @@ Feature: Test basic usage of workshop activity in app
| \mod_workshop\event\course_module_viewed | workshop | Test workshop | Course 1 |
Scenario: Prefetch a workshop
- Given I entered the workshop activity "workshop" on course "Course 1" as "teacher1" in the app
+ Given I entered the course "Course 1" as "teacher1" in the app
+ And I press "workshop" in the app
When I press "Information" in the app
And I press "Download" in the app
And I press "Close" in the app
diff --git a/src/core/features/contentlinks/services/contentlinks-delegate.ts b/src/core/features/contentlinks/services/contentlinks-delegate.ts
index 2b3e1a93c..d76bb7ca0 100644
--- a/src/core/features/contentlinks/services/contentlinks-delegate.ts
+++ b/src/core/features/contentlinks/services/contentlinks-delegate.ts
@@ -19,6 +19,7 @@ import { CoreUrl } from '@singletons/url';
import { makeSingleton } from '@singletons';
import { CoreText } from '@singletons/text';
import { CorePromiseUtils } from '@singletons/promise-utils';
+import { CoreNavigator } from '@services/navigator';
/**
* Interface that all handlers must implement.
@@ -208,24 +209,24 @@ export class CoreContentLinksDelegateService {
// Wrap the action function in our own function to treat logged out sites.
const actionFunction = action.action;
action.action = async (siteId) => {
- const site = await CoreSites.getSite(siteId);
+ if (!CoreSites.isLoggedIn()) {
+ // Not logged in, load site first.
+ const loggedIn = await CoreSites.loadSite(siteId, { urlToOpen: url });
+ if (loggedIn) {
+ await CoreNavigator.navigateToSiteHome({ params: { urlToOpen: url } });
+ }
- if (!site.isLoggedOut()) {
- // Call the action now.
- return actionFunction(siteId);
+ return;
}
- // Site is logged out, authenticate first before treating the URL.
- const willReload = await CoreSites.logoutForRedirect(siteId, {
- urlToOpen: url,
- });
+ if (siteId !== CoreSites.getCurrentSiteId()) {
+ // Different site, logout and login first before treating the URL because token could be expired.
+ await CoreSites.logout({ urlToOpen: url, siteId });
- if (!willReload) {
- // Load the site with the redirect data.
- await CoreSites.loadSite(siteId, {
- urlToOpen: url,
- });
+ return;
}
+
+ actionFunction(siteId);
};
});
diff --git a/src/core/features/login/login.module.ts b/src/core/features/login/login.module.ts
index b0d3d8fbd..8030f6411 100644
--- a/src/core/features/login/login.module.ts
+++ b/src/core/features/login/login.module.ts
@@ -41,6 +41,10 @@ const appRoutes: Routes = [
loadChildren: () => import('./login-lazy.module'),
canActivate: [redirectGuard],
},
+ {
+ path: 'logout',
+ loadComponent: () => import('@features/login/pages/logout/logout'),
+ },
];
@NgModule({
diff --git a/src/core/features/login/pages/logout/logout.html b/src/core/features/login/pages/logout/logout.html
new file mode 100644
index 000000000..1e61e89a5
--- /dev/null
+++ b/src/core/features/login/pages/logout/logout.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/core/features/login/pages/logout/logout.ts b/src/core/features/login/pages/logout/logout.ts
new file mode 100644
index 000000000..db0bc10ff
--- /dev/null
+++ b/src/core/features/login/pages/logout/logout.ts
@@ -0,0 +1,104 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Component, OnInit } from '@angular/core';
+import { CoreSites } from '@services/sites';
+import { CoreConstants } from '@/core/constants';
+import { CoreNavigationOptions, CoreNavigator, CoreRedirectPayload } from '@services/navigator';
+import { CoreSharedModule } from '@/core/shared.module';
+import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
+import { CoreRedirects } from '@singletons/redirects';
+
+/**
+ * Page that logs the user out.
+ */
+@Component({
+ selector: 'page-core-login-logout',
+ templateUrl: 'logout.html',
+ standalone: true,
+ imports: [
+ CoreSharedModule,
+ ],
+})
+export default class CoreLoginLogoutPage implements OnInit {
+
+ /**
+ * @inheritdoc
+ */
+ async ngOnInit(): Promise {
+ const siteId = CoreNavigator.getRouteParam('siteId') ?? CoreConstants.NO_SITE_ID;
+ const logoutOptions = {
+ forceLogout: CoreNavigator.getRouteBooleanParam('forceLogout'),
+ removeAccount: CoreNavigator.getRouteBooleanParam('removeAccount') ?? !!CoreConstants.CONFIG.removeaccountonlogout,
+ };
+ const redirectData = {
+ redirectPath: CoreNavigator.getRouteParam('redirectPath'),
+ redirectOptions: CoreNavigator.getRouteParam('redirectOptions'),
+ urlToOpen: CoreNavigator.getRouteParam('urlToOpen'),
+ };
+
+ if (!CoreSites.isLoggedIn()) {
+ // This page shouldn't open if user isn't logged in, but if that happens just navigate to the right page.
+ await this.navigateAfterLogout(siteId, redirectData);
+
+ return;
+ }
+
+ const shouldReload = CoreSitePlugins.hasSitePluginsLoaded;
+ if (shouldReload && (siteId !== CoreConstants.NO_SITE_ID || redirectData.redirectPath || redirectData.urlToOpen)) {
+ // The app will reload and we need to open a page that isn't the default page. Store the redirect first.
+ CoreRedirects.storeRedirect(siteId, redirectData);
+ }
+
+ await CoreSites.internalLogout(logoutOptions);
+
+ if (shouldReload) {
+ // We need to reload the app to unload all the plugins. Leave the logout page first.
+ await CoreNavigator.navigate('/login', { reset: true });
+
+ window.location.reload();
+
+ return;
+ }
+
+ await this.navigateAfterLogout(siteId, redirectData);
+ }
+
+ /**
+ * Navigate to the right page after logout is done.
+ *
+ * @param siteId Site ID to load.
+ * @param redirectData Redirect data.
+ */
+ protected async navigateAfterLogout(siteId: string, redirectData: CoreRedirectPayload): Promise {
+ if (siteId === CoreConstants.NO_SITE_ID) {
+ // No site to load now, just navigate.
+ await CoreNavigator.navigate(redirectData.redirectPath ?? '/login/sites', {
+ ...redirectData.redirectOptions,
+ reset: true,
+ });
+
+ return;
+ }
+
+ // Load the site and navigate.
+ const loggedIn = await CoreSites.loadSite(siteId, redirectData);
+ if (!loggedIn) {
+ return; // Session expired.
+ }
+
+ await CoreNavigator.navigateToSiteHome({ params: redirectData, preferCurrentTab: false, siteId });
+ }
+
+}
diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts
index 9be527c82..23ee93791 100644
--- a/src/core/features/login/services/login-helper.ts
+++ b/src/core/features/login/services/login-helper.ts
@@ -412,22 +412,19 @@ export class CoreLoginHelperProvider {
* @returns Promise resolved when done.
*/
async goToAddSite(setRoot = false, showKeyboard = false): Promise {
- let path = '/login/sites';
- let params: Params = { openAddSite: true , showKeyboard };
-
if (CoreSites.isLoggedIn()) {
- const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
- redirectPath: path,
- redirectOptions: { params },
+ // Logout first.
+ await CoreSites.logout({
+ siteId: CoreConstants.NO_SITE_ID,
+ redirectPath: '/login/sites',
+ redirectOptions: { params: { openAddSite: true , showKeyboard } },
});
- if (willReload) {
- return;
- }
- } else {
- [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
+ return;
}
+ const [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
+
await CoreNavigator.navigate(path, { params, reset: setRoot });
}
diff --git a/src/core/features/login/tests/behat/basic_usage.feature b/src/core/features/login/tests/behat/basic_usage.feature
index 2285a9d93..39bde7341 100755
--- a/src/core/features/login/tests/behat/basic_usage.feature
+++ b/src/core/features/login/tests/behat/basic_usage.feature
@@ -66,13 +66,6 @@ Feature: Test basic usage of login in app
And I press "Connect to your site" in the app
Then I should find "Can't connect to site" in the app
- Scenario: Log out from the app
- Given I entered the app as "student1"
- And I press the user menu button in the app
- When I press "Log out" in the app
- And I wait the app to restart
- Then the header should be "Accounts" in the app
-
Scenario: Delete an account
Given I entered the app as "student1"
When I log out in the app
diff --git a/src/core/features/login/tests/behat/logout.feature b/src/core/features/login/tests/behat/logout.feature
new file mode 100755
index 000000000..15b1ebb73
--- /dev/null
+++ b/src/core/features/login/tests/behat/logout.feature
@@ -0,0 +1,160 @@
+@core_login @app @javascript
+Feature: Test different cases of logout and switch account
+ I need different logout use cases to work
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname |
+ | student1 | david | student |
+ | student2 | pau | student2 |
+
+ Scenario: Log out and re-login
+ Given I entered the app as "student1"
+ When I press the user menu button in the app
+ And I press "Log out" in the app
+ And I wait the app to restart
+ Then the header should be "Accounts" in the app
+
+ When I press "david student" in the app
+ Then the header should be "Reconnect" in the app
+ And I should find "david student" in the app
+
+ When I set the following fields to these values in the app:
+ | Password | student1 |
+ And I press "Log in" near "Lost password?" in the app
+ Then the header should be "Acceptance test site" in the app
+
+ Scenario: Exit account using switch account and re-enter
+ Given I entered the app as "student1"
+ When I press the user menu button in the app
+ And I press "Switch account" in the app
+ And I press "Add" in the app
+ And I wait the app to restart
+ Then I should find "Connect to Moodle" in the app
+
+ When I go back in the app
+ And I press "david student" in the app
+ Then the header should be "Acceptance test site" in the app
+
+ Scenario: Exit account using switch account and re-enter when forcelogout is enabled
+ Given the following config values are set as admin:
+ | forcelogout | 1 | tool_mobile |
+ And I entered the app as "student1"
+ When I press the user menu button in the app
+ And I press "Switch account" in the app
+ And I press "Add" in the app
+ And I wait the app to restart
+ And I go back in the app
+ And I press "david student" in the app
+ Then the header should be "Reconnect" in the app
+ And I should find "david student" in the app
+
+ Scenario: Switch to a different account
+ Given I entered the app as "student1"
+ And I entered the app as "student2"
+ When I press the user menu button in the app
+ Then I should find "pau student2" in the app
+
+ When I press "Switch account" in the app
+ And I press "david student" in the app
+ And I wait the app to restart
+ Then the header should be "Acceptance test site" in the app
+
+ When I press the user menu button in the app
+ Then I should find "david student" in the app
+
+ Scenario: Logout when there is unsaved data
+ Given the following "courses" exist:
+ | fullname | shortname |
+ | Course 1 | C1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | student1 | C1 | student |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber |
+ | forum | Test forum | Test forum | C1 | forum |
+ And the following forum discussions exist in course "Course 1":
+ | forum | user | name | message |
+ | Test forum | student1 | Forum topic 1 | Forum message 1 |
+ | Test forum | student1 | Forum topic 2 | Forum message 2 |
+ And I entered the course "Course 1" as "student1" in the app
+ And I change viewport size to "1200x640" in the app
+
+ When I press "Test forum" in the app
+ And I press "Add discussion topic" in the app
+ And I set the following fields to these values in the app:
+ | Subject | My happy subject |
+ | Message | An awesome message |
+ And I press the user menu button in the app
+ And I press "Log out" in the app
+ Then I should find "Are you sure you want to leave this page?" in the app
+
+ # Check that the app continues working fine if the user cancels the logout.
+ When I press "Cancel" in the app
+ And I press "Forum topic 1" in the app
+ And I press "OK" in the app
+ Then I should find "Forum message 1" in the app
+
+ When I press "Forum topic 2" in the app
+ Then I should find "Forum message 2" in the app
+
+ # Now confirm the logout.
+ When I press "Add discussion topic" in the app
+ And I set the following fields to these values in the app:
+ | Subject | My happy subject |
+ | Message | An awesome message |
+ And I press the user menu button in the app
+ And I press "Log out" in the app
+ And I press "OK" in the app
+ And I wait the app to restart
+ Then the header should be "Accounts" in the app
+
+ Scenario: Switch account when there is unsaved data
+ Given the following "courses" exist:
+ | fullname | shortname |
+ | Course 1 | C1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | student1 | C1 | student |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber |
+ | forum | Test forum | Test forum | C1 | forum |
+ And the following forum discussions exist in course "Course 1":
+ | forum | user | name | message |
+ | Test forum | student1 | Forum topic 1 | Forum message 1 |
+ | Test forum | student1 | Forum topic 2 | Forum message 2 |
+ And I entered the app as "student2"
+ And I entered the course "Course 1" as "student1" in the app
+ And I change viewport size to "1200x640" in the app
+
+ When I press "Test forum" in the app
+ And I press "Add discussion topic" in the app
+ And I set the following fields to these values in the app:
+ | Subject | My happy subject |
+ | Message | An awesome message |
+ And I press the user menu button in the app
+ And I press "Switch account" in the app
+ And I press "pau student2" in the app
+ Then I should find "Are you sure you want to leave this page?" in the app
+
+ # Check that the app continues working fine if the user cancels the switch account.
+ When I press "Cancel" in the app
+ And I press "Forum topic 1" in the app
+ And I press "OK" in the app
+ Then I should find "Forum message 1" in the app
+
+ When I press "Forum topic 2" in the app
+ Then I should find "Forum message 2" in the app
+
+ # Now confirm the switch account.
+ When I press "Add discussion topic" in the app
+ And I set the following fields to these values in the app:
+ | Subject | My happy subject |
+ | Message | An awesome message |
+ And I press the user menu button in the app
+ And I press "Switch account" in the app
+ And I press "pau student2" in the app
+ And I press "OK" in the app
+ And I wait the app to restart
+ And I press the user menu button in the app
+ Then I should find "pau student2" in the app
diff --git a/src/core/features/login/tests/behat/showloginform_setting.feature b/src/core/features/login/tests/behat/showloginform_setting.feature
index 7da2d8b77..0c6a2581c 100644
--- a/src/core/features/login/tests/behat/showloginform_setting.feature
+++ b/src/core/features/login/tests/behat/showloginform_setting.feature
@@ -48,7 +48,7 @@ Feature: Test showloginform setting in the app
And I press "Moodle Mobile" in the app
And I press "Developer options" in the app
And I press "Always show login form" in the app
- And I go back 4 times in the app
+ And I go back to the root page in the app
And I press "david student" in the app
Then the header should be "Reconnect" in the app
And I should find "Log in" "ion-button" in the app
diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.ts b/src/core/features/mainmenu/components/user-menu/user-menu.ts
index e52071150..1aecec73f 100644
--- a/src/core/features/mainmenu/components/user-menu/user-menu.ts
+++ b/src/core/features/mainmenu/components/user-menu/user-menu.ts
@@ -18,7 +18,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { CoreSite } from '@classes/sites/site';
import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
import { CoreFilter } from '@features/filter/services/filter';
-import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
import { CoreUserSupport } from '@features/user/services/support';
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
@@ -33,8 +32,9 @@ import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CorePromiseUtils } from '@singletons/promise-utils';
-import { ModalController, Translate } from '@singletons';
+import { ModalController } from '@singletons';
import { Subscription } from 'rxjs';
+import { CoreLoginHelper } from '@features/login/services/login-helper';
/**
* Component to display a user menu.
@@ -208,12 +208,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
* @param event Click event
*/
async logout(event: Event): Promise {
- if (CoreNavigator.currentRouteCanBlockLeave()) {
- await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
-
- return;
- }
-
if (this.removeAccountOnLogout) {
// Ask confirm.
const siteName = this.siteName ?
@@ -242,12 +236,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
* @param event Click event
*/
async switchAccounts(event: Event): Promise {
- if (CoreNavigator.currentRouteCanBlockLeave()) {
- await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
-
- return;
- }
-
const thisModal = await ModalController.getTop();
event.preventDefault();
diff --git a/src/core/features/policy/pages/site-policy/site-policy.ts b/src/core/features/policy/pages/site-policy/site-policy.ts
index 94be08c61..dfab2c14f 100644
--- a/src/core/features/policy/pages/site-policy/site-policy.ts
+++ b/src/core/features/policy/pages/site-policy/site-policy.ts
@@ -280,9 +280,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy {
* @returns Promise resolved when done.
*/
async cancel(): Promise {
- await CorePromiseUtils.ignoreErrors(CoreSites.logout());
-
- await CoreNavigator.navigate('/login/sites', { reset: true });
+ await CoreSites.logout();
}
/**
diff --git a/src/core/features/settings/tests/behat/sync.feature b/src/core/features/settings/tests/behat/sync.feature
index 22020eec2..564ab37b1 100644
--- a/src/core/features/settings/tests/behat/sync.feature
+++ b/src/core/features/settings/tests/behat/sync.feature
@@ -19,7 +19,8 @@ Feature: It synchronise sites properly
Scenario: Sync the current site
# Add something offline
- Given I entered the choice activity "Sync choice" on course "Course 1" as "student1" in the app
+ Given I entered the course "Course 1" as "student1" in the app
+ And I press "Sync choice" in the app
When I switch network connection to offline
And I select "Option 1" in the app
And I press "Save my choice" in the app
diff --git a/src/core/features/sitehome/tests/links.test.ts b/src/core/features/sitehome/tests/links.test.ts
index f137e69ec..c0f58e06f 100644
--- a/src/core/features/sitehome/tests/links.test.ts
+++ b/src/core/features/sitehome/tests/links.test.ts
@@ -32,6 +32,8 @@ describe('Site Home link handlers', () => {
isStoredRootURL: () => Promise.resolve({ siteIds: [siteId] }),
getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')),
getSiteIdsFromUrl: () => Promise.resolve([siteId]),
+ getCurrentSiteId: () => siteId,
+ isLoggedIn: () => true,
}));
mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] });
diff --git a/src/core/services/navigator.ts b/src/core/services/navigator.ts
index 6632359f7..0c840b043 100644
--- a/src/core/services/navigator.ts
+++ b/src/core/services/navigator.ts
@@ -195,7 +195,7 @@ export class CoreNavigatorService {
async navigateToSiteHome(options: Omit & { siteId?: string } = {}): Promise {
const siteId = options.siteId ?? CoreSites.getCurrentSiteId();
const landingPagePath = CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() === siteId ?
- this.getLandingTabPage() : 'main';
+ this.getLandingTabPage() : '';
return this.navigateToSitePath(landingPagePath, {
...options,
@@ -220,14 +220,12 @@ export class CoreNavigatorService {
// If we are logged into a different site, log out first.
if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
- const willReload = await CoreSites.logoutForRedirect(siteId, {
- redirectPath: path,
- redirectOptions: options || {},
+ await CoreSites.logout({
+ ...this.getRedirectDataForSitePath(path, options),
+ siteId,
});
- if (willReload) {
- return true;
- }
+ return true;
}
// If the path doesn't belong to a site, call standard navigation.
@@ -243,10 +241,7 @@ export class CoreNavigatorService {
const modal = await CoreLoadings.show();
try {
- const loggedIn = await CoreSites.loadSite(siteId, {
- redirectPath: path,
- redirectOptions: options,
- });
+ const loggedIn = await CoreSites.loadSite(siteId, this.getRedirectDataForSitePath(path, options));
if (!loggedIn) {
// User has been redirected to the login page and will be redirected to the site path after login.
@@ -264,6 +259,31 @@ export class CoreNavigatorService {
return this.navigateToMainMenuPath(path, navigationOptions);
}
+ /**
+ * Get the redirect data to use when navigating to a site path.
+ *
+ * @param path Site path.
+ * @param options Navigation options.
+ * @returns Redirect data.
+ */
+ protected getRedirectDataForSitePath(path: string, options: CoreNavigationOptions = {}): CoreRedirectPayload {
+ if (!path || path.match(/^\/?main\/?$/)) {
+ // Navigating to main, obtain the redirect from the navigation parameters (if any).
+ // If there is no redirect path or url to open, use 'main' to open the site's main menu.
+ return {
+ redirectPath: !options.params?.redirectPath && !options.params?.urlToOpen ? 'main' : options.params?.redirectPath,
+ redirectOptions: options.params?.redirectOptions,
+ urlToOpen: options.params?.urlToOpen,
+ };
+ }
+
+ // Use the path to navigate as the redirect path.
+ return {
+ redirectPath: path,
+ redirectOptions: options || {},
+ };
+ }
+
/**
* Get the active route path.
*
@@ -547,6 +567,11 @@ export class CoreNavigatorService {
...options,
};
+ if (!path || path.match(/^\/?main\/?$/)) {
+ // Navigating to main, nothing else to do.
+ return this.navigate('/main', options);
+ }
+
path = path.replace(/^(\.|\/main)?\//, '');
const pathRoot = /^[^/]+/.exec(path)?.[0] ?? '';
diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts
index 1dfdd725e..b761cc9f6 100644
--- a/src/core/services/sites.ts
+++ b/src/core/services/sites.ts
@@ -141,14 +141,6 @@ export class CoreSitesProvider {
// Remove version classes from body.
CoreHTMLClasses.removeSiteClasses();
-
- // Go to sites page when user is logged out.
- await CoreNavigator.navigate('/login/sites', { reset: true });
-
- if (CoreSitePlugins.hasSitePluginsLoaded) {
- // Temporary fix. Reload the page to unload all plugins.
- window.location.reload();
- }
});
CoreEvents.on(CoreEvents.LOGIN, async (data) => {
@@ -964,7 +956,7 @@ export class CoreSitesProvider {
promise.finally(() => {
if (siteId) {
// Logout the currentSite and expire the token.
- this.logout();
+ this.internalLogout();
this.setSiteLoggedOut(siteId);
}
});
@@ -1123,7 +1115,7 @@ export class CoreSitesProvider {
this.logger.debug(`Delete site ${siteId}`);
if (this.currentSite !== undefined && this.currentSite.id == siteId) {
- this.logout();
+ this.internalLogout();
}
const site = await this.getSite(siteId);
@@ -1457,10 +1449,23 @@ export class CoreSitesProvider {
/**
* Logout the user.
*
- * @param options Logout options.
- * @returns Promise resolved when the user is logged out.
+ * @param options Options.
*/
async logout(options: CoreSitesLogoutOptions = {}): Promise {
+ await CoreNavigator.navigate('/logout', {
+ params: { ...options },
+ reset: true,
+ });
+ }
+
+ /**
+ * Logout the user.
+ * This function is for internal usage, please use CoreSites.logout instead. The reason this function is public is because
+ * it's called from the CoreLoginLogoutPage page.
+ *
+ * @param options Logout options.
+ */
+ async internalLogout(options: InternalLogoutOptions = {}): Promise {
if (!this.currentSite) {
return;
}
@@ -1494,6 +1499,7 @@ export class CoreSitesProvider {
* @param siteId Site that will be opened after logout.
* @param redirectData Page/url to open after logout.
* @returns Promise resolved with boolean: true if app will be reloaded after logout.
+ * @deprecated since 5.0. Use CoreSites.logout instead, it automatically handles redirects.
*/
async logoutForRedirect(siteId: string, redirectData: CoreRedirectPayload): Promise {
if (!this.currentSite) {
@@ -1505,7 +1511,7 @@ export class CoreSitesProvider {
CoreRedirects.storeRedirect(siteId, redirectData);
}
- await this.logout();
+ await this.internalLogout();
return CoreSitePlugins.hasSitePluginsLoaded;
}
@@ -2480,7 +2486,14 @@ export type CoreSitesLoginTokenResponse = {
/**
* Options for logout.
*/
-export type CoreSitesLogoutOptions = {
+export type CoreSitesLogoutOptions = CoreRedirectPayload & InternalLogoutOptions & {
+ siteId?: string; // Site ID to load after logout.
+};
+
+/**
+ * Options for internal logout.
+ */
+type InternalLogoutOptions = {
forceLogout?: boolean; // If true, site will be marked as logged out, no matter the value tool_mobile_forcelogout.
removeAccount?: boolean; // If true, site will be removed too after logout.
};
diff --git a/src/core/services/tests/sites.test.ts b/src/core/services/tests/sites.test.ts
index 68e477405..961c84708 100644
--- a/src/core/services/tests/sites.test.ts
+++ b/src/core/services/tests/sites.test.ts
@@ -16,7 +16,6 @@ import { CoreEvents } from '@singletons/events';
import { CoreLang, CoreLangProvider } from '@services/lang';
import { mock, mockSingleton } from '@/testing/utils';
-import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { Http } from '@singletons';
import { of } from 'rxjs';
@@ -34,13 +33,10 @@ describe('CoreSitesProvider', () => {
});
it('cleans up on logout', async () => {
- const navigator: CoreNavigatorService = mockSingleton(CoreNavigator, ['navigate']);
-
CoreSites.initialize();
CoreEvents.trigger(CoreEvents.LOGOUT);
expect(langProvider.clearCustomStrings).toHaveBeenCalled();
- expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
});
it('adds ionic platform and theme classes', async () => {
diff --git a/src/core/services/urlschemes.ts b/src/core/services/urlschemes.ts
index 65cd87bc4..833bdcc8e 100644
--- a/src/core/services/urlschemes.ts
+++ b/src/core/services/urlschemes.ts
@@ -432,14 +432,13 @@ export class CoreCustomURLSchemesProvider {
// Ask the user before changing site.
await CoreDomUtils.showConfirm(Translate.instant('core.contentlinks.confirmurlothersite'));
- const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
+ await CoreSites.logout({
+ siteId: CoreConstants.NO_SITE_ID,
redirectPath: '/login/credentials',
redirectOptions: { params: pageParams },
});
- if (willReload) {
- return;
- }
+ return;
}
await CoreNavigator.navigateToLoginCredentials(pageParams);
diff --git a/src/core/tests/behat/navigation_deeplinks.feature b/src/core/tests/behat/navigation_deeplinks.feature
index 41bd254c2..7f858d53b 100644
--- a/src/core/tests/behat/navigation_deeplinks.feature
+++ b/src/core/tests/behat/navigation_deeplinks.feature
@@ -3,9 +3,9 @@ Feature: It navigates properly using deep links.
Background:
Given the following "users" exist:
- | username |
- | student1 |
- | student2 |
+ | username | firstname | lastname |
+ | student1 | david | student |
+ | student2 | pau | student2 |
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
@@ -20,7 +20,6 @@ Feature: It navigates properly using deep links.
| forum | user | name | message |
| Test forum | student1 | Forum topic | Forum message |
And the following config values are set as admin:
- | forcelogout | 1 | tool_mobile |
| defaulthomepage | 0 | |
Scenario: Receive a push notification
@@ -78,3 +77,98 @@ Feature: It navigates properly using deep links.
When I go back in the app
Then I should find "Site home" in the app
But I should not find "Test forum" in the app
+
+ Scenario: Open a deep link in a different account not stored in the app
+ Given I entered the app as "student1"
+ When I open a custom link in the app for:
+ | discussion | user |
+ | Forum topic | student2 |
+ Then I should find "This link belongs to another site" in the app
+
+ When I press "OK" in the app
+ And I wait the app to restart
+ Then the header should be "Log in" in the app
+
+ When I set the following fields to these values in the app:
+ | Password | student2 |
+ And I press "Log in" near "Lost password?" in the app
+ Then I should find "Forum topic" in the app
+ And I should find "Forum message" in the app
+
+ When I go back to the root page in the app
+ And I press the user menu button in the app
+ Then I should find "pau student2" in the app
+
+ Scenario: Open a deep link in a different account stored in the app
+ Given I entered the app as "student2"
+ And I entered the app as "student1"
+ When I open a custom link in the app for:
+ | discussion | user |
+ | Forum topic | student2 |
+ Then I should find "This link belongs to another site" in the app
+
+ When I press "OK" in the app
+ And I wait the app to restart
+ Then I should find "Forum topic" in the app
+ And I should find "Forum message" in the app
+
+ When I go back to the root page in the app
+ And I press the user menu button in the app
+ Then I should find "pau student2" in the app
+
+ Scenario: Open a deep link in a different account stored in the app but logged out
+ Given I entered the app as "student2"
+ And I press the user menu button in the app
+ And I press "Log out" in the app
+ And I wait the app to restart
+ And I entered the app as "student1"
+ When I open a custom link in the app for:
+ | discussion | user |
+ | Forum topic | student2 |
+ Then I should find "This link belongs to another site" in the app
+
+ When I press "OK" in the app
+ And I wait the app to restart
+ Then the header should be "Reconnect" in the app
+ And I should find "pau student2" in the app
+
+ When I set the following fields to these values in the app:
+ | Password | student2 |
+ And I press "Log in" near "Lost password?" in the app
+ Then I should find "Forum topic" in the app
+ And I should find "Forum message" in the app
+
+ When I go back to the root page in the app
+ And I press the user menu button in the app
+ Then I should find "pau student2" in the app
+
+ Scenario: Open a deep link in a different account when there is unsaved data
+ Given I entered the app as "student2"
+ And I entered the forum activity "Test forum" on course "Course 1" as "student1" in the app
+ When I press "Add discussion topic" in the app
+ And I set the following fields to these values in the app:
+ | Subject | My happy subject |
+ | Message | An awesome message |
+ And I open a custom link in the app for:
+ | discussion | user |
+ | Forum topic | student2 |
+ Then I should find "This link belongs to another site" in the app
+
+ When I press "OK" in the app
+ Then I should find "Are you sure you want to leave this page?" in the app
+
+ When I press "Cancel" in the app
+ Then I should not find "Forum message" in the app
+
+ When I open a custom link in the app for:
+ | discussion | user |
+ | Forum topic | student2 |
+ And I press "OK" in the app
+ And I press "OK" in the app
+ And I wait the app to restart
+ Then I should find "Forum topic" in the app
+ And I should find "Forum message" in the app
+
+ When I go back to the root page in the app
+ And I press the user menu button in the app
+ Then I should find "pau student2" in the app
diff --git a/src/core/tests/behat/open_files.feature b/src/core/tests/behat/open_files.feature
index 9e7e097b9..9f675e067 100644
--- a/src/core/tests/behat/open_files.feature
+++ b/src/core/tests/behat/open_files.feature
@@ -21,7 +21,8 @@ Feature: It opens files properly.
| resource | Test DOC | Test DOC description | 5 | C1 | A doc.doc |
And the following config values are set as admin:
| filetypeexclusionlist | rtf,doc | tool_mobile |
- And I entered the resource activity "Test TXT" on course "Course 1" as "student1" in the app
+ And I entered the course "Course 1" as "student1" in the app
+ And I press "Test TXT" in the app
When I press "Open" in the app
Then the app should have opened a browser tab with url "^blob:"
diff --git a/src/testing/services/behat-runtime.ts b/src/testing/services/behat-runtime.ts
index f6c7d7ed0..74d0b717a 100644
--- a/src/testing/services/behat-runtime.ts
+++ b/src/testing/services/behat-runtime.ts
@@ -136,19 +136,39 @@ export class TestingBehatRuntimeService {
* Run an operation inside the angular zone and return result.
*
* @param operation Operation callback.
+ * @param blocking Whether the operation is blocking or not.
+ * @param locatorToFind If set, when this locator is found the operation is considered finished. This is useful for
+ * operations that might expect user input before finishing, like a confirm modal.
* @returns OK if successful, or ERROR: followed by message.
*/
- async runInZone(operation: () => unknown, blocking: boolean = false): Promise {
+ async runInZone(
+ operation: () => unknown,
+ blocking: boolean = false,
+ locatorToFind?: TestingBehatElementLocator,
+ ): Promise {
const blockKey = blocking && TestingBehatBlocking.block();
+ let interval: number | undefined;
try {
- await NgZone.run(operation);
+ await new Promise((resolve, reject) => {
+ Promise.resolve(NgZone.run(operation)).then(resolve).catch(reject);
+
+ if (locatorToFind) {
+ interval = window.setInterval(() => {
+ if (TestingBehatDomUtils.findElementBasedOnText(locatorToFind, { onlyClickable: false })) {
+ clearInterval(interval);
+ resolve();
+ }
+ }, 500);
+ }
+ });
return 'OK';
} catch (error) {
return 'ERROR: ' + error.message;
} finally {
blockKey && TestingBehatBlocking.unblock(blockKey);
+ window.clearInterval(interval);
}
}
diff --git a/upgrade.txt b/upgrade.txt
index edb6425e0..a9429f01f 100644
--- a/upgrade.txt
+++ b/upgrade.txt
@@ -2,6 +2,10 @@ This file describes API changes in the Moodle App that affect site plugins, info
For more information about upgrading, read the official documentation: https://moodledev.io/general/app/upgrading/
+=== 5.0.0 ===
+
+ - The logout process has been refactored, now it uses a logout page to trigger Angular guards. CoreSites.logout now uses this process, and CoreSites.logoutForRedirect is deprecated and shouldn't be used anymore.
+
=== 4.5.0 ===
- Ionic has been upgraded to major version 8. See breaking changes and upgrade guide here: https://ionicframework.com/docs/updating/8-0