forked from CIT/Vmeda.Online
		
	
						commit
						476a1795ae
					
				@ -604,6 +604,7 @@ class behat_app extends behat_app_helper {
 | 
				
			|||||||
        $data = $data->getColumnsHash()[0];
 | 
					        $data = $data->getColumnsHash()[0];
 | 
				
			||||||
        $title = array_keys($data)[0];
 | 
					        $title = array_keys($data)[0];
 | 
				
			||||||
        $data = (object) $data;
 | 
					        $data = (object) $data;
 | 
				
			||||||
 | 
					        $username = $data->user ?? '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        switch ($title) {
 | 
					        switch ($title) {
 | 
				
			||||||
            case 'discussion':
 | 
					            case 'discussion':
 | 
				
			||||||
@ -645,7 +646,7 @@ class behat_app extends behat_app_helper {
 | 
				
			|||||||
                throw new DriverException('Invalid custom link title - ' . $title);
 | 
					                throw new DriverException('Invalid custom link title - ' . $title);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->open_moodleapp_custom_url($pageurl);
 | 
					        $this->open_moodleapp_custom_url($pageurl, '', $username);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -366,12 +366,15 @@ class behat_app_helper extends behat_base {
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param string $script
 | 
					     * @param string $script
 | 
				
			||||||
     * @param bool $blocking
 | 
					     * @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.
 | 
					     * @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);
 | 
					        $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;
 | 
					            $privatetoken = $usertoken->privatetoken;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Generate custom URL.
 | 
					        $url = $this->generate_custom_url([
 | 
				
			||||||
        $parsed_url = parse_url($CFG->behat_wwwroot);
 | 
					            'username' => $username,
 | 
				
			||||||
        $site = $parsed_url['host'] ?? '';
 | 
					            'token' => $token,
 | 
				
			||||||
        $site .= isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
 | 
					            'privatetoken' => $privatetoken,
 | 
				
			||||||
        $site .= $parsed_url['path'] ?? '';
 | 
					            'redirect' => $path,
 | 
				
			||||||
        $url = $this->get_mobile_url_scheme() . "://$username@$site?token=$token&privatetoken=$privatetoken";
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!empty($path)) {
 | 
					        if (empty($path)) {
 | 
				
			||||||
            $url .= '&redirect='.urlencode($CFG->behat_wwwroot.$path);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            $successXPath = '//page-core-mainmenu';
 | 
					            $successXPath = '//page-core-mainmenu';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -434,14 +435,54 @@ class behat_app_helper extends behat_base {
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param string $path To navigate.
 | 
					     * @param string $path To navigate.
 | 
				
			||||||
     * @param string $successXPath The XPath of the element to lookat after navigation.
 | 
					     * @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;
 | 
					        global $CFG;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $urlscheme = $this->get_mobile_url_scheme();
 | 
					        $url = $this->generate_custom_url([
 | 
				
			||||||
        $url = "$urlscheme://link=" . urlencode($CFG->behat_wwwroot.$path);
 | 
					            '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 $customurl To navigate.
 | 
				
			||||||
     * @param string $successXPath The XPath of the element to lookat after navigation.
 | 
					     * @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 = '') {
 | 
					    protected function handle_url(string $customurl, string $successXPath = '', string $texttofind = '') {
 | 
				
			||||||
        $result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')");
 | 
					        $result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')", false, $texttofind);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($result !== 'OK') {
 | 
					        if ($result !== 'OK') {
 | 
				
			||||||
            throw new DriverException('Error handling url - ' . $customurl . ' - '.$result);
 | 
					            throw new DriverException('Error handling url - ' . $customurl . ' - '.$result);
 | 
				
			||||||
 | 
				
			|||||||
@ -132,7 +132,8 @@ Feature: Test basic usage of assignment activity in app
 | 
				
			|||||||
    Then I should find "No attempt" in the app
 | 
					    Then I should find "No attempt" in the app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: Add submission offline (online text) & Submit for grading offline & Sync submissions
 | 
					  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
 | 
					    When I press "Add submission" in the app
 | 
				
			||||||
    And I switch network connection to offline
 | 
					    And I switch network connection to offline
 | 
				
			||||||
    And I set the field "Online text submissions" to "Submission test" in the app
 | 
					    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
 | 
					    But I should not find "This Assignment has offline data to be synchronised." in the app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: Edit an offline submission before synchronising it
 | 
					  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
 | 
					    When I press "Add submission" in the app
 | 
				
			||||||
    And I switch network connection to offline
 | 
					    And I switch network connection to offline
 | 
				
			||||||
    And I set the field "Online text submissions" to "Submission test original offline" in the app
 | 
					    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
 | 
					  @lms_from4.5
 | 
				
			||||||
  Scenario: Remove submission offline and syncrhonize it
 | 
					  Scenario: Remove submission offline and syncrhonize 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 "Add submission" 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 set the field "Online text submissions" to "Submission test" in the app
 | 
				
			||||||
    And I press "Save" in the app
 | 
					    And I press "Save" in the app
 | 
				
			||||||
    Then I should find "Draft (not submitted)" 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
 | 
					  @lms_from4.5
 | 
				
			||||||
  Scenario: Add submission offline after removing a submission offline
 | 
					  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
 | 
					    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 set the field "Online text submissions" to "Submission test online" in the app
 | 
				
			||||||
    And I press "Save" in the app
 | 
					    And I press "Save" in the app
 | 
				
			||||||
 | 
				
			|||||||
@ -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 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 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 ## |
 | 
					      | 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
 | 
					    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
 | 
					    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 & recordings | C1     | bbb1     | 0    |
 | 
				
			||||||
      | bigbluebuttonbn | Room only         | C1     | bbb2     | 1    |
 | 
					      | bigbluebuttonbn | Room only         | C1     | bbb2     | 1    |
 | 
				
			||||||
      | bigbluebuttonbn | Recordings only   | C1     | bbb3     | 2    |
 | 
					      | 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
 | 
					    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 be able to press "Join session" in the app
 | 
				
			||||||
    And I should find "Recordings" in the app
 | 
					    And I should find "Recordings" in the app
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,8 @@ Feature: Test basic usage of choice activity in app
 | 
				
			|||||||
    Given the following "activities" exist:
 | 
					    Given the following "activities" exist:
 | 
				
			||||||
      | activity | name                    | intro                          | course | idnumber | option                       | allowmultiple | allowupdate | showresults |
 | 
					      | 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           |
 | 
					      | 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
 | 
					    When I select "Option 1" in the app
 | 
				
			||||||
    And I select "Option 2" in the app
 | 
					    And I select "Option 2" in the app
 | 
				
			||||||
    And I press "Save my choice" 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:
 | 
					    Given the following "activities" exist:
 | 
				
			||||||
      | activity | name                    | intro                          | course | idnumber | option                       | allowmultiple | allowupdate | showresults |
 | 
					      | 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           |
 | 
					      | 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
 | 
					    When I select "Option 1" in the app
 | 
				
			||||||
    And I switch network connection to offline
 | 
					    And I switch network connection to offline
 | 
				
			||||||
    And I select "Option 2" in the app
 | 
					    And I select "Option 2" in the app
 | 
				
			||||||
 | 
				
			|||||||
@ -28,9 +28,11 @@ Feature: Users can store entries in database activities when offline and sync wh
 | 
				
			|||||||
      | data1    | text | Description | Link description |
 | 
					      | data1    | text | Description | Link description |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: Create entry (offline)
 | 
					  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 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
 | 
					    When I press "Add entry" in the app
 | 
				
			||||||
    And I set the following fields to these values in the app:
 | 
					    And I set the following fields to these values in the app:
 | 
				
			||||||
      | URL | https://moodle.org/ |
 | 
					      | 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
 | 
					    Then I should find "https://moodle.org/" in the app
 | 
				
			||||||
    And I should find "Moodle community site" 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 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 switch network connection to wifi
 | 
				
			||||||
    And I press "Web links" near "General" in the app
 | 
					    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 find "Moodle community site" in the app
 | 
				
			||||||
    And I should not find "This Database has offline data to be synchronised" 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)
 | 
					  Scenario: Update entry (offline) & Delete 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 should find "No entries yet" in the app
 | 
					    And I press "Web links" in the app
 | 
				
			||||||
    And I press "Add entry" 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:
 | 
					    And I set the following fields to these values in the app:
 | 
				
			||||||
      | URL | https://moodle.org/ |
 | 
					      | URL | https://moodle.org/ |
 | 
				
			||||||
      | Description | Moodle community site |
 | 
					      | Description | Moodle community site |
 | 
				
			||||||
    And I press "Save" near "Web links" in the app
 | 
					    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 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 press "Download" in the app
 | 
				
			||||||
    And I wait until the page is ready
 | 
					    And I wait until the page is ready
 | 
				
			||||||
    And I close the popup in the app
 | 
					    And I close the popup in the app
 | 
				
			||||||
    And I switch network connection to offline
 | 
					    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 press "Edit" in the app
 | 
				
			||||||
    And I set the following fields to these values in the app:
 | 
					    And I set the following fields to these values in the app:
 | 
				
			||||||
      | URL | https://moodlecloud.com/ |
 | 
					      | 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 "https://moodlecloud.com/" in the app
 | 
				
			||||||
    And I should find "Moodle Cloud" 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 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 switch network connection to wifi
 | 
				
			||||||
    And I press "Web links" near "General" in the app
 | 
					    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 not find "Moodle community site" in the app
 | 
				
			||||||
    And I should find "https://moodlecloud.com/" 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 find "Moodle Cloud" in the app
 | 
				
			||||||
    And I should not find "This Database has offline data to be synchronised" 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 press "Refresh" in the app
 | 
				
			||||||
    And I wait until the page is ready
 | 
					    And I wait until the page is ready
 | 
				
			||||||
    And I switch network connection to offline
 | 
					    And I switch network connection to offline
 | 
				
			||||||
    And I press "Actions menu" in the app
 | 
					    And I press "Actions menu" in the app
 | 
				
			||||||
    And I press "Delete" 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
 | 
					    Then 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
 | 
					    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 "Moodle Cloud" in the app
 | 
				
			||||||
    And I should find "This Database has offline data to be synchronised" 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 switch network connection to wifi
 | 
				
			||||||
    And I press "Web links" near "General" in the app
 | 
					    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 "Moodle Cloud" in the app
 | 
				
			||||||
    And I should not find "This Database has offline data to be synchronised" 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
 | 
					  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
 | 
					    Given I entered the course "Course 1" as "student1" in the app
 | 
				
			||||||
    And I should find "No entries yet" in the app
 | 
					    And I press "Web links" in the app
 | 
				
			||||||
    And I press "Add entry" 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:
 | 
					    And I set the following fields to these values in the app:
 | 
				
			||||||
      | URL | https://moodle.org/ |
 | 
					      | URL | https://moodle.org/ |
 | 
				
			||||||
      | Description | Moodle community site |
 | 
					      | Description | Moodle community site |
 | 
				
			||||||
    And I press "Save" near "Web links" in the app
 | 
					    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 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 press "Download" in the app
 | 
				
			||||||
    And I wait until the page is ready
 | 
					    And I wait until the page is ready
 | 
				
			||||||
    And I close the popup in the app
 | 
					    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 "Actions menu" in the app
 | 
				
			||||||
    And I press "Delete" 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
 | 
					    Then 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
 | 
					    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 "Moodle community site" in the app
 | 
				
			||||||
    And I should find "This Database has offline data to be synchronised" 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 press "Restore" in the app
 | 
				
			||||||
    And I go back in the app
 | 
					    And I go back in the app
 | 
				
			||||||
    And I switch network connection to wifi
 | 
					    And I switch network connection to wifi
 | 
				
			||||||
 | 
				
			|||||||
@ -276,7 +276,8 @@ Feature: Test basic usage of forum activity in app
 | 
				
			|||||||
    But I should not find "Not sent" in the app
 | 
					    But I should not find "Not sent" in the app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: New discussion offline & Sync Forum
 | 
					  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
 | 
					    When I switch network connection to offline
 | 
				
			||||||
    And I press "Add discussion topic" in the app
 | 
					    And I press "Add discussion topic" in the app
 | 
				
			||||||
    And I set the following fields to these values in the app:
 | 
					    And I set the following fields to these values in the app:
 | 
				
			||||||
 | 
				
			|||||||
@ -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 |
 | 
					      | forum2 | Disc vis ALL | Disc vis ALL | Disc vis ALL content | All participants |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: Student can only see the right groups
 | 
					  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
 | 
					    Then I should find "Disc sep G1" in the app
 | 
				
			||||||
    And I should find "Disc sep ALL" in the app
 | 
					    And I should find "Disc sep ALL" in the app
 | 
				
			||||||
    But I should not find "Disc sep G2" 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
 | 
					    But I should not find "Disc vis G2" in the app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: Teacher can see all groups
 | 
					  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
 | 
					    When I press "Separate groups" in the app
 | 
				
			||||||
    Then I should find "All participants" in the app
 | 
					    Then I should find "All participants" in the app
 | 
				
			||||||
    And I should find "Group 1" 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
 | 
					    But I should not find "Disc vis G2" in the app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: Student can only add discussions in his groups
 | 
					  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
 | 
					    When I press "Add discussion topic" in the app
 | 
				
			||||||
    And I press "Advanced" in the app
 | 
					    And I press "Advanced" in the app
 | 
				
			||||||
    Then I should not find "Post a copy to all groups" 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
 | 
					    Then I should find "My happy subject" in the app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: Teacher can add discussion to any group
 | 
					  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
 | 
					    Given I entered the course "Course 1" as "teacher1" in the app
 | 
				
			||||||
    And I press "Separate groups" 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 "All participants" in the app
 | 
				
			||||||
    And I press "Add discussion topic" in the app
 | 
					    And I press "Add discussion topic" in the app
 | 
				
			||||||
    And I press "Advanced" 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
 | 
					    Then I should find "My third subject" in the app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: Teacher can post a copy in all groups
 | 
					  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
 | 
					    Given I entered the course "Course 1" as "teacher1" in the app
 | 
				
			||||||
    And I press "Separate groups" 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 "Group 1" in the app
 | 
				
			||||||
    And I press "Add discussion topic" in the app
 | 
					    And I press "Add discussion topic" in the app
 | 
				
			||||||
    And I press "Advanced" in the app
 | 
					    And I press "Advanced" in the app
 | 
				
			||||||
 | 
				
			|||||||
@ -52,7 +52,8 @@ Feature: Test basic usage of glossary in app
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  Scenario: Navigate to glossary terms by link (auto-linking)
 | 
					  Scenario: Navigate to glossary terms by link (auto-linking)
 | 
				
			||||||
    Given the "glossary" filter is "on"
 | 
					    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
 | 
					    Then the header should be "Test glossary" in the app
 | 
				
			||||||
    And I should find "Eggplant" in the app
 | 
					    And I should find "Eggplant" in the app
 | 
				
			||||||
    And I should find "Cucumber" in the app
 | 
					    And I should find "Cucumber" in the app
 | 
				
			||||||
 | 
				
			|||||||
@ -49,8 +49,9 @@ Feature: Users can only review attempts that are allowed to be reviewed
 | 
				
			|||||||
      | 1    | True     |
 | 
					      | 1    | True     |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: Can review only when the attempt is allowed to be reviewed
 | 
					  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
 | 
					    Given I entered the course "Course 1" as "student1" in the app
 | 
				
			||||||
    And I press "Attempt 1" 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
 | 
					    Then I should not be able to press "Review" in the app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    When I go back in the app
 | 
					    When I go back in the app
 | 
				
			||||||
 | 
				
			|||||||
@ -238,8 +238,9 @@ Feature: Test basic usage of survey activity in app
 | 
				
			|||||||
    Given the following "activities" exist:
 | 
					    Given the following "activities" exist:
 | 
				
			||||||
      | activity | name                           | intro        | template | course | idnumber | groupmode |
 | 
					      | activity | name                           | intro        | template | course | idnumber | groupmode |
 | 
				
			||||||
      | survey   | Test survey critical incidents | Test survey1 | 5        | C1     | survey1  | 0         |
 | 
					      | 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 entered the course "Course 1" as "student1" in the app
 | 
				
			||||||
    And I switch network connection to offline
 | 
					    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 "Submit" in the app
 | 
				
			||||||
    And I press "OK" in the app
 | 
					    And I press "OK" in the app
 | 
				
			||||||
    Then I should see "This Survey has offline data to be synchronised."
 | 
					    Then I should see "This Survey has offline data to be synchronised."
 | 
				
			||||||
 | 
				
			|||||||
@ -130,7 +130,8 @@ Feature: Test basic usage of workshop activity in app
 | 
				
			|||||||
      | \mod_workshop\event\course_module_viewed | workshop | Test workshop | Course 1 |
 | 
					      | \mod_workshop\event\course_module_viewed | workshop | Test workshop | Course 1 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: Prefetch a workshop
 | 
					  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
 | 
					    When I press "Information" in the app
 | 
				
			||||||
    And I press "Download" in the app
 | 
					    And I press "Download" in the app
 | 
				
			||||||
    And I press "Close" in the app
 | 
					    And I press "Close" in the app
 | 
				
			||||||
 | 
				
			|||||||
@ -19,6 +19,7 @@ import { CoreUrl } from '@singletons/url';
 | 
				
			|||||||
import { makeSingleton } from '@singletons';
 | 
					import { makeSingleton } from '@singletons';
 | 
				
			||||||
import { CoreText } from '@singletons/text';
 | 
					import { CoreText } from '@singletons/text';
 | 
				
			||||||
import { CorePromiseUtils } from '@singletons/promise-utils';
 | 
					import { CorePromiseUtils } from '@singletons/promise-utils';
 | 
				
			||||||
 | 
					import { CoreNavigator } from '@services/navigator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Interface that all handlers must implement.
 | 
					 * 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.
 | 
					                        // Wrap the action function in our own function to treat logged out sites.
 | 
				
			||||||
                        const actionFunction = action.action;
 | 
					                        const actionFunction = action.action;
 | 
				
			||||||
                        action.action = async (siteId) => {
 | 
					                        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()) {
 | 
					                                return;
 | 
				
			||||||
                                // Call the action now.
 | 
					 | 
				
			||||||
                                return actionFunction(siteId);
 | 
					 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            // Site is logged out, authenticate first before treating the URL.
 | 
					                            if (siteId !== CoreSites.getCurrentSiteId()) {
 | 
				
			||||||
                            const willReload = await CoreSites.logoutForRedirect(siteId, {
 | 
					                                // Different site, logout and login first before treating the URL because token could be expired.
 | 
				
			||||||
                                urlToOpen: url,
 | 
					                                await CoreSites.logout({ urlToOpen: url, siteId });
 | 
				
			||||||
                            });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if (!willReload) {
 | 
					                                return;
 | 
				
			||||||
                                // Load the site with the redirect data.
 | 
					 | 
				
			||||||
                                await CoreSites.loadSite(siteId, {
 | 
					 | 
				
			||||||
                                    urlToOpen: url,
 | 
					 | 
				
			||||||
                                });
 | 
					 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            actionFunction(siteId);
 | 
				
			||||||
                        };
 | 
					                        };
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -41,6 +41,10 @@ const appRoutes: Routes = [
 | 
				
			|||||||
        loadChildren: () => import('./login-lazy.module'),
 | 
					        loadChildren: () => import('./login-lazy.module'),
 | 
				
			||||||
        canActivate: [redirectGuard],
 | 
					        canActivate: [redirectGuard],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        path: 'logout',
 | 
				
			||||||
 | 
					        loadComponent: () => import('@features/login/pages/logout/logout'),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@NgModule({
 | 
					@NgModule({
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								src/core/features/login/pages/logout/logout.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/core/features/login/pages/logout/logout.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					<ion-content>
 | 
				
			||||||
 | 
					    <core-loading />
 | 
				
			||||||
 | 
					</ion-content>
 | 
				
			||||||
							
								
								
									
										104
									
								
								src/core/features/login/pages/logout/logout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/core/features/login/pages/logout/logout.ts
									
									
									
									
									
										Normal file
									
								
							@ -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<void> {
 | 
				
			||||||
 | 
					        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<CoreNavigationOptions>('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<void> {
 | 
				
			||||||
 | 
					        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 });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -412,22 +412,19 @@ export class CoreLoginHelperProvider {
 | 
				
			|||||||
     * @returns Promise resolved when done.
 | 
					     * @returns Promise resolved when done.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async goToAddSite(setRoot = false, showKeyboard = false): Promise<void> {
 | 
					    async goToAddSite(setRoot = false, showKeyboard = false): Promise<void> {
 | 
				
			||||||
        let path = '/login/sites';
 | 
					 | 
				
			||||||
        let params: Params = { openAddSite: true , showKeyboard };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (CoreSites.isLoggedIn()) {
 | 
					        if (CoreSites.isLoggedIn()) {
 | 
				
			||||||
            const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
 | 
					            // Logout first.
 | 
				
			||||||
                redirectPath: path,
 | 
					            await CoreSites.logout({
 | 
				
			||||||
                redirectOptions: { params },
 | 
					                siteId: CoreConstants.NO_SITE_ID,
 | 
				
			||||||
 | 
					                redirectPath: '/login/sites',
 | 
				
			||||||
 | 
					                redirectOptions: { params: { openAddSite: true , showKeyboard } },
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (willReload) {
 | 
					            return;
 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await CoreNavigator.navigate(path, { params, reset: setRoot });
 | 
					        await CoreNavigator.navigate(path, { params, reset: setRoot });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -66,13 +66,6 @@ Feature: Test basic usage of login in app
 | 
				
			|||||||
    And I press "Connect to your site" in the app
 | 
					    And I press "Connect to your site" in the app
 | 
				
			||||||
    Then I should find "Can't connect to 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
 | 
					  Scenario: Delete an account
 | 
				
			||||||
    Given I entered the app as "student1"
 | 
					    Given I entered the app as "student1"
 | 
				
			||||||
    When I log out in the app
 | 
					    When I log out in the app
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										160
									
								
								src/core/features/login/tests/behat/logout.feature
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										160
									
								
								src/core/features/login/tests/behat/logout.feature
									
									
									
									
									
										Executable file
									
								
							@ -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
 | 
				
			||||||
@ -48,7 +48,7 @@ Feature: Test showloginform setting in the app
 | 
				
			|||||||
    And I press "Moodle Mobile" in the app
 | 
					    And I press "Moodle Mobile" in the app
 | 
				
			||||||
    And I press "Developer options" in the app
 | 
					    And I press "Developer options" in the app
 | 
				
			||||||
    And I press "Always show login form" 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
 | 
					    And I press "david student" in the app
 | 
				
			||||||
    Then the header should be "Reconnect" in the app
 | 
					    Then the header should be "Reconnect" in the app
 | 
				
			||||||
    And I should find "Log in" "ion-button" in the app
 | 
					    And I should find "Log in" "ion-button" in the app
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
 | 
				
			|||||||
import { CoreSite } from '@classes/sites/site';
 | 
					import { CoreSite } from '@classes/sites/site';
 | 
				
			||||||
import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
 | 
					import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
 | 
				
			||||||
import { CoreFilter } from '@features/filter/services/filter';
 | 
					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 { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
 | 
				
			||||||
import { CoreUserSupport } from '@features/user/services/support';
 | 
					import { CoreUserSupport } from '@features/user/services/support';
 | 
				
			||||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
 | 
					import { CoreUser, CoreUserProfile } from '@features/user/services/user';
 | 
				
			||||||
@ -33,8 +32,9 @@ import { CoreNavigator } from '@services/navigator';
 | 
				
			|||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
import { CorePromiseUtils } from '@singletons/promise-utils';
 | 
					import { CorePromiseUtils } from '@singletons/promise-utils';
 | 
				
			||||||
import { ModalController, Translate } from '@singletons';
 | 
					import { ModalController } from '@singletons';
 | 
				
			||||||
import { Subscription } from 'rxjs';
 | 
					import { Subscription } from 'rxjs';
 | 
				
			||||||
 | 
					import { CoreLoginHelper } from '@features/login/services/login-helper';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component to display a user menu.
 | 
					 * Component to display a user menu.
 | 
				
			||||||
@ -208,12 +208,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
     * @param event Click event
 | 
					     * @param event Click event
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async logout(event: Event): Promise<void> {
 | 
					    async logout(event: Event): Promise<void> {
 | 
				
			||||||
        if (CoreNavigator.currentRouteCanBlockLeave()) {
 | 
					 | 
				
			||||||
            await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (this.removeAccountOnLogout) {
 | 
					        if (this.removeAccountOnLogout) {
 | 
				
			||||||
            // Ask confirm.
 | 
					            // Ask confirm.
 | 
				
			||||||
            const siteName = this.siteName ?
 | 
					            const siteName = this.siteName ?
 | 
				
			||||||
@ -242,12 +236,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
     * @param event Click event
 | 
					     * @param event Click event
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async switchAccounts(event: Event): Promise<void> {
 | 
					    async switchAccounts(event: Event): Promise<void> {
 | 
				
			||||||
        if (CoreNavigator.currentRouteCanBlockLeave()) {
 | 
					 | 
				
			||||||
            await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const thisModal = await ModalController.getTop();
 | 
					        const thisModal = await ModalController.getTop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        event.preventDefault();
 | 
					        event.preventDefault();
 | 
				
			||||||
 | 
				
			|||||||
@ -280,9 +280,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy {
 | 
				
			|||||||
     * @returns Promise resolved when done.
 | 
					     * @returns Promise resolved when done.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async cancel(): Promise<void> {
 | 
					    async cancel(): Promise<void> {
 | 
				
			||||||
        await CorePromiseUtils.ignoreErrors(CoreSites.logout());
 | 
					        await CoreSites.logout();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        await CoreNavigator.navigate('/login/sites', { reset: true });
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,8 @@ Feature: It synchronise sites properly
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  Scenario: Sync the current site
 | 
					  Scenario: Sync the current site
 | 
				
			||||||
    # Add something offline
 | 
					    # 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
 | 
					    When I switch network connection to offline
 | 
				
			||||||
    And I select "Option 1" in the app
 | 
					    And I select "Option 1" in the app
 | 
				
			||||||
    And I press "Save my choice" in the app
 | 
					    And I press "Save my choice" in the app
 | 
				
			||||||
 | 
				
			|||||||
@ -32,6 +32,8 @@ describe('Site Home link handlers', () => {
 | 
				
			|||||||
            isStoredRootURL: () => Promise.resolve({ siteIds: [siteId] }),
 | 
					            isStoredRootURL: () => Promise.resolve({ siteIds: [siteId] }),
 | 
				
			||||||
            getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')),
 | 
					            getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')),
 | 
				
			||||||
            getSiteIdsFromUrl: () => Promise.resolve([siteId]),
 | 
					            getSiteIdsFromUrl: () => Promise.resolve([siteId]),
 | 
				
			||||||
 | 
					            getCurrentSiteId: () => siteId,
 | 
				
			||||||
 | 
					            isLoggedIn: () => true,
 | 
				
			||||||
        }));
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] });
 | 
					        mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] });
 | 
				
			||||||
 | 
				
			|||||||
@ -195,7 +195,7 @@ export class CoreNavigatorService {
 | 
				
			|||||||
    async navigateToSiteHome(options: Omit<CoreNavigationOptions, 'reset'> & { siteId?: string } = {}): Promise<boolean> {
 | 
					    async navigateToSiteHome(options: Omit<CoreNavigationOptions, 'reset'> & { siteId?: string } = {}): Promise<boolean> {
 | 
				
			||||||
        const siteId = options.siteId ?? CoreSites.getCurrentSiteId();
 | 
					        const siteId = options.siteId ?? CoreSites.getCurrentSiteId();
 | 
				
			||||||
        const landingPagePath = CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() === siteId ?
 | 
					        const landingPagePath = CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() === siteId ?
 | 
				
			||||||
            this.getLandingTabPage() : 'main';
 | 
					            this.getLandingTabPage() : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.navigateToSitePath(landingPagePath, {
 | 
					        return this.navigateToSitePath(landingPagePath, {
 | 
				
			||||||
            ...options,
 | 
					            ...options,
 | 
				
			||||||
@ -220,14 +220,12 @@ export class CoreNavigatorService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // If we are logged into a different site, log out first.
 | 
					        // If we are logged into a different site, log out first.
 | 
				
			||||||
        if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
 | 
					        if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
 | 
				
			||||||
            const willReload = await CoreSites.logoutForRedirect(siteId, {
 | 
					            await CoreSites.logout({
 | 
				
			||||||
                redirectPath: path,
 | 
					                ...this.getRedirectDataForSitePath(path, options),
 | 
				
			||||||
                redirectOptions: options || {},
 | 
					                siteId,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (willReload) {
 | 
					            return true;
 | 
				
			||||||
                return true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // If the path doesn't belong to a site, call standard navigation.
 | 
					        // If the path doesn't belong to a site, call standard navigation.
 | 
				
			||||||
@ -243,10 +241,7 @@ export class CoreNavigatorService {
 | 
				
			|||||||
            const modal = await CoreLoadings.show();
 | 
					            const modal = await CoreLoadings.show();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                const loggedIn = await CoreSites.loadSite(siteId, {
 | 
					                const loggedIn = await CoreSites.loadSite(siteId, this.getRedirectDataForSitePath(path, options));
 | 
				
			||||||
                    redirectPath: path,
 | 
					 | 
				
			||||||
                    redirectOptions: options,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (!loggedIn) {
 | 
					                if (!loggedIn) {
 | 
				
			||||||
                    // User has been redirected to the login page and will be redirected to the site path after login.
 | 
					                    // 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);
 | 
					        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.
 | 
					     * Get the active route path.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -547,6 +567,11 @@ export class CoreNavigatorService {
 | 
				
			|||||||
            ...options,
 | 
					            ...options,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!path || path.match(/^\/?main\/?$/)) {
 | 
				
			||||||
 | 
					            // Navigating to main, nothing else to do.
 | 
				
			||||||
 | 
					            return this.navigate('/main', options);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        path = path.replace(/^(\.|\/main)?\//, '');
 | 
					        path = path.replace(/^(\.|\/main)?\//, '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const pathRoot = /^[^/]+/.exec(path)?.[0] ?? '';
 | 
					        const pathRoot = /^[^/]+/.exec(path)?.[0] ?? '';
 | 
				
			||||||
 | 
				
			|||||||
@ -141,14 +141,6 @@ export class CoreSitesProvider {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            // Remove version classes from body.
 | 
					            // Remove version classes from body.
 | 
				
			||||||
            CoreHTMLClasses.removeSiteClasses();
 | 
					            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) => {
 | 
					        CoreEvents.on(CoreEvents.LOGIN, async (data) => {
 | 
				
			||||||
@ -964,7 +956,7 @@ export class CoreSitesProvider {
 | 
				
			|||||||
            promise.finally(() => {
 | 
					            promise.finally(() => {
 | 
				
			||||||
                if (siteId) {
 | 
					                if (siteId) {
 | 
				
			||||||
                    // Logout the currentSite and expire the token.
 | 
					                    // Logout the currentSite and expire the token.
 | 
				
			||||||
                    this.logout();
 | 
					                    this.internalLogout();
 | 
				
			||||||
                    this.setSiteLoggedOut(siteId);
 | 
					                    this.setSiteLoggedOut(siteId);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -1123,7 +1115,7 @@ export class CoreSitesProvider {
 | 
				
			|||||||
        this.logger.debug(`Delete site ${siteId}`);
 | 
					        this.logger.debug(`Delete site ${siteId}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.currentSite !== undefined && this.currentSite.id == siteId) {
 | 
					        if (this.currentSite !== undefined && this.currentSite.id == siteId) {
 | 
				
			||||||
            this.logout();
 | 
					            this.internalLogout();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const site = await this.getSite(siteId);
 | 
					        const site = await this.getSite(siteId);
 | 
				
			||||||
@ -1457,10 +1449,23 @@ export class CoreSitesProvider {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Logout the user.
 | 
					     * Logout the user.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param options Logout options.
 | 
					     * @param options Options.
 | 
				
			||||||
     * @returns Promise resolved when the user is logged out.
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async logout(options: CoreSitesLogoutOptions = {}): Promise<void> {
 | 
					    async logout(options: CoreSitesLogoutOptions = {}): Promise<void> {
 | 
				
			||||||
 | 
					        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<void> {
 | 
				
			||||||
        if (!this.currentSite) {
 | 
					        if (!this.currentSite) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -1494,6 +1499,7 @@ export class CoreSitesProvider {
 | 
				
			|||||||
     * @param siteId Site that will be opened after logout.
 | 
					     * @param siteId Site that will be opened after logout.
 | 
				
			||||||
     * @param redirectData Page/url to open after logout.
 | 
					     * @param redirectData Page/url to open after logout.
 | 
				
			||||||
     * @returns Promise resolved with boolean: true if app will be reloaded 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<boolean> {
 | 
					    async logoutForRedirect(siteId: string, redirectData: CoreRedirectPayload): Promise<boolean> {
 | 
				
			||||||
        if (!this.currentSite) {
 | 
					        if (!this.currentSite) {
 | 
				
			||||||
@ -1505,7 +1511,7 @@ export class CoreSitesProvider {
 | 
				
			|||||||
            CoreRedirects.storeRedirect(siteId, redirectData);
 | 
					            CoreRedirects.storeRedirect(siteId, redirectData);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await this.logout();
 | 
					        await this.internalLogout();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return CoreSitePlugins.hasSitePluginsLoaded;
 | 
					        return CoreSitePlugins.hasSitePluginsLoaded;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -2480,7 +2486,14 @@ export type CoreSitesLoginTokenResponse = {
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Options for logout.
 | 
					 * 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.
 | 
					    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.
 | 
					    removeAccount?: boolean; // If true, site will be removed too after logout.
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,6 @@ import { CoreEvents } from '@singletons/events';
 | 
				
			|||||||
import { CoreLang, CoreLangProvider } from '@services/lang';
 | 
					import { CoreLang, CoreLangProvider } from '@services/lang';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { mock, mockSingleton } from '@/testing/utils';
 | 
					import { mock, mockSingleton } from '@/testing/utils';
 | 
				
			||||||
import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
 | 
					 | 
				
			||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { Http } from '@singletons';
 | 
					import { Http } from '@singletons';
 | 
				
			||||||
import { of } from 'rxjs';
 | 
					import { of } from 'rxjs';
 | 
				
			||||||
@ -34,13 +33,10 @@ describe('CoreSitesProvider', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('cleans up on logout', async () => {
 | 
					    it('cleans up on logout', async () => {
 | 
				
			||||||
        const navigator: CoreNavigatorService = mockSingleton(CoreNavigator, ['navigate']);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        CoreSites.initialize();
 | 
					        CoreSites.initialize();
 | 
				
			||||||
        CoreEvents.trigger(CoreEvents.LOGOUT);
 | 
					        CoreEvents.trigger(CoreEvents.LOGOUT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(langProvider.clearCustomStrings).toHaveBeenCalled();
 | 
					        expect(langProvider.clearCustomStrings).toHaveBeenCalled();
 | 
				
			||||||
        expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('adds ionic platform and theme classes', async () => {
 | 
					    it('adds ionic platform and theme classes', async () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -432,14 +432,13 @@ export class CoreCustomURLSchemesProvider {
 | 
				
			|||||||
            // Ask the user before changing site.
 | 
					            // Ask the user before changing site.
 | 
				
			||||||
            await CoreDomUtils.showConfirm(Translate.instant('core.contentlinks.confirmurlothersite'));
 | 
					            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',
 | 
					                redirectPath: '/login/credentials',
 | 
				
			||||||
                redirectOptions: { params: pageParams },
 | 
					                redirectOptions: { params: pageParams },
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (willReload) {
 | 
					            return;
 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await CoreNavigator.navigateToLoginCredentials(pageParams);
 | 
					        await CoreNavigator.navigateToLoginCredentials(pageParams);
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,9 @@ Feature: It navigates properly using deep links.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  Background:
 | 
					  Background:
 | 
				
			||||||
    Given the following "users" exist:
 | 
					    Given the following "users" exist:
 | 
				
			||||||
      | username |
 | 
					      | username | firstname | lastname |
 | 
				
			||||||
      | student1 |
 | 
					      | student1 | david     | student  |
 | 
				
			||||||
      | student2 |
 | 
					      | student2 | pau       | student2 |
 | 
				
			||||||
    And the following "courses" exist:
 | 
					    And the following "courses" exist:
 | 
				
			||||||
      | fullname | shortname |
 | 
					      | fullname | shortname |
 | 
				
			||||||
      | Course 1 | C1        |
 | 
					      | Course 1 | C1        |
 | 
				
			||||||
@ -20,7 +20,6 @@ Feature: It navigates properly using deep links.
 | 
				
			|||||||
      | forum      | user     | name        | message       |
 | 
					      | forum      | user     | name        | message       |
 | 
				
			||||||
      | Test forum | student1 | Forum topic | Forum message |
 | 
					      | Test forum | student1 | Forum topic | Forum message |
 | 
				
			||||||
    And the following config values are set as admin:
 | 
					    And the following config values are set as admin:
 | 
				
			||||||
      | forcelogout     | 1 | tool_mobile |
 | 
					 | 
				
			||||||
      | defaulthomepage | 0 |             |
 | 
					      | defaulthomepage | 0 |             |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Scenario: Receive a push notification
 | 
					  Scenario: Receive a push notification
 | 
				
			||||||
@ -78,3 +77,98 @@ Feature: It navigates properly using deep links.
 | 
				
			|||||||
    When I go back in the app
 | 
					    When I go back in the app
 | 
				
			||||||
    Then I should find "Site home" in the app
 | 
					    Then I should find "Site home" in the app
 | 
				
			||||||
    But I should not find "Test forum" 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
 | 
				
			||||||
 | 
				
			|||||||
@ -21,7 +21,8 @@ Feature: It opens files properly.
 | 
				
			|||||||
      | resource | Test DOC | Test DOC description | 5       | C1     | A doc.doc       |
 | 
					      | resource | Test DOC | Test DOC description | 5       | C1     | A doc.doc       |
 | 
				
			||||||
    And the following config values are set as admin:
 | 
					    And the following config values are set as admin:
 | 
				
			||||||
      | filetypeexclusionlist | rtf,doc | tool_mobile |
 | 
					      | 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
 | 
					    When I press "Open" in the app
 | 
				
			||||||
    Then the app should have opened a browser tab with url "^blob:"
 | 
					    Then the app should have opened a browser tab with url "^blob:"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -136,19 +136,39 @@ export class TestingBehatRuntimeService {
 | 
				
			|||||||
     * Run an operation inside the angular zone and return result.
 | 
					     * Run an operation inside the angular zone and return result.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param operation Operation callback.
 | 
					     * @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.
 | 
					     * @returns OK if successful, or ERROR: followed by message.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async runInZone(operation: () => unknown, blocking: boolean = false): Promise<string> {
 | 
					    async runInZone(
 | 
				
			||||||
 | 
					        operation: () => unknown,
 | 
				
			||||||
 | 
					        blocking: boolean = false,
 | 
				
			||||||
 | 
					        locatorToFind?: TestingBehatElementLocator,
 | 
				
			||||||
 | 
					    ): Promise<string> {
 | 
				
			||||||
        const blockKey = blocking && TestingBehatBlocking.block();
 | 
					        const blockKey = blocking && TestingBehatBlocking.block();
 | 
				
			||||||
 | 
					        let interval: number | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            await NgZone.run(operation);
 | 
					            await new Promise<void>((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';
 | 
					            return 'OK';
 | 
				
			||||||
        } catch (error) {
 | 
					        } catch (error) {
 | 
				
			||||||
            return 'ERROR: ' + error.message;
 | 
					            return 'ERROR: ' + error.message;
 | 
				
			||||||
        } finally {
 | 
					        } finally {
 | 
				
			||||||
            blockKey && TestingBehatBlocking.unblock(blockKey);
 | 
					            blockKey && TestingBehatBlocking.unblock(blockKey);
 | 
				
			||||||
 | 
					            window.clearInterval(interval);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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/
 | 
					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 ===
 | 
					=== 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
 | 
					 - Ionic has been upgraded to major version 8. See breaking changes and upgrade guide here: https://ionicframework.com/docs/updating/8-0
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user