Merge branch 'primary_secondary_nav' of https://github.com/Chocolate-lightning/moodle
This commit is contained in:
+6
-1
@@ -46,6 +46,8 @@ if ($data = data_submitted() and confirm_sesskey() and isset($data->action) and
|
||||
}
|
||||
}
|
||||
|
||||
$PAGE->has_secondary_navigation_setter(false);
|
||||
|
||||
// and finally, if we get here, then there are matching settings and we have to print a form
|
||||
// to modify them
|
||||
echo $OUTPUT->header($focus);
|
||||
@@ -88,7 +90,10 @@ if ($hassiteconfig) {
|
||||
if ($showsettingslinks) {
|
||||
$node = $PAGE->settingsnav->find('root', navigation_node::TYPE_SITE_ADMIN);
|
||||
if ($node) {
|
||||
echo $OUTPUT->render_from_template('core/settings_link_page', ['node' => $node]);
|
||||
$moremenu = new \core\navigation\output\more_menu($PAGE->secondarynav, 'nav-tabs');
|
||||
$secondarynavigation = $moremenu->export_for_template($OUTPUT);
|
||||
echo $OUTPUT->render_from_template('core/settings_link_page',
|
||||
['node' => $node, 'secondarynavigation' => $secondarynavigation]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Feature: Verify that keyboard steps work as expected
|
||||
| username | email | firstname | lastname |
|
||||
| saffronr | saffron.rutledge@example.com | Saffron | Rutledge |
|
||||
And I log in as "saffronr"
|
||||
And I click on "Saffron Rutledge" "link" in the ".usermenu" "css_element"
|
||||
And I click on "Saffron Rutledge" "button" in the ".usermenu" "css_element"
|
||||
When I press the up key
|
||||
Then the focused element is "Log out" "link"
|
||||
|
||||
|
||||
@@ -14,20 +14,20 @@ Feature: Within a moodle instance, an administrator should be able to modify lan
|
||||
And I press "Open language pack for editing"
|
||||
And I press "Continue"
|
||||
And I set the field "Show strings of these components" to "moodle.php"
|
||||
And I set the field "String identifier" to "administrationsite"
|
||||
And I set the field "String identifier" to "moodledocslink"
|
||||
And I press "Show strings"
|
||||
And I set the field "core/administrationsite" to "Custom string example"
|
||||
And I set the field "core/moodledocslink" to "moodle documents"
|
||||
|
||||
@javascript
|
||||
Scenario: Edit an string but don't save it to lang pack.
|
||||
When I press "Apply changes and continue editing"
|
||||
Then I should see "Site administration" in the "page-header" "region"
|
||||
And I should not see "Custom string example" in the "page-header" "region"
|
||||
Then I should see "Help and documentation" in the ".helplink" "css_element"
|
||||
And I should not see "moodle documents" in the ".helplink" "css_element"
|
||||
|
||||
@javascript
|
||||
Scenario: Customize an string as admin and save it to lang pack.
|
||||
Given I press "Save changes to the language pack"
|
||||
And I should see "There are 1 modified strings."
|
||||
When I click on "Continue" "button"
|
||||
Then I should see "Custom string example" in the "page-header" "region"
|
||||
And I should not see "Site administration" in the "page-header" "region"
|
||||
Then I should see "moodle documents" in the ".helplink" "css_element"
|
||||
And I should not see "Help and documentation" in the ".helplink" "css_element"
|
||||
|
||||
@@ -24,7 +24,7 @@ Feature: Within a moodle instance, an administrator should be able to import mod
|
||||
And I should see "There are 1 modified strings."
|
||||
And I click on "Save strings to language pack" "button"
|
||||
And I click on "Continue" "button"
|
||||
And I should see "An amazing import feature" in the "page-header" "region"
|
||||
And I should see "An amazing import feature"
|
||||
|
||||
@javascript
|
||||
Scenario: Try to import a PHP file from a non existent component
|
||||
@@ -46,6 +46,6 @@ Feature: Within a moodle instance, an administrator should be able to import mod
|
||||
And I should see "There are 3 modified strings."
|
||||
And I click on "Save strings to language pack" "button"
|
||||
And I click on "Continue" "button"
|
||||
And I should see "Uploaded custom string" in the "page-header" "region"
|
||||
And I should see "Another Uploaded string" in the "page-header" "region"
|
||||
And I should see "An amazing import feature" in the "page-header" "region"
|
||||
And I should see "Uploaded custom string"
|
||||
And I should see "Another Uploaded string"
|
||||
And I should see "An amazing import feature"
|
||||
|
||||
@@ -21,7 +21,7 @@ Feature: Within a moodle instance, an administrator should be able to import lan
|
||||
And I press "Save changes to the language pack"
|
||||
And I should see "There are 1 modified strings."
|
||||
And I click on "Continue" "button"
|
||||
And I should see "Custom string example" in the "page-header" "region"
|
||||
And I should see "Custom string example"
|
||||
|
||||
@javascript
|
||||
Scenario: Update only customized strings
|
||||
@@ -36,12 +36,12 @@ Feature: Within a moodle instance, an administrator should be able to import lan
|
||||
And I should see "String core/nonexistentinvetedstring not found."
|
||||
And I click on "Continue" "button"
|
||||
And I should see "There are 1 modified strings."
|
||||
And I should not see "Uploaded custom string" in the "page-header" "region"
|
||||
And I should not see "Uploaded custom string"
|
||||
And I click on "Save strings to language pack" "button"
|
||||
And I click on "Continue" "button"
|
||||
And I should not see "Custom string example" in the "page-header" "region"
|
||||
And I should see "Uploaded custom string" in the "page-header" "region"
|
||||
And I should not see "Another Uploaded string" in the "page-header" "region"
|
||||
And I should not see "Custom string example"
|
||||
And I should see "Uploaded custom string"
|
||||
And I should not see "Another Uploaded string"
|
||||
|
||||
@javascript
|
||||
Scenario: Create only new strings
|
||||
@@ -56,12 +56,12 @@ Feature: Within a moodle instance, an administrator should be able to import lan
|
||||
And I should see "String core/nonexistentinvetedstring not found."
|
||||
And I click on "Continue" "button"
|
||||
And I should see "There are 1 modified strings."
|
||||
And I should not see "Uploaded custom string" in the "page-header" "region"
|
||||
And I should not see "Uploaded custom string"
|
||||
And I click on "Save strings to language pack" "button"
|
||||
And I click on "Continue" "button"
|
||||
And I should see "Custom string example" in the "page-header" "region"
|
||||
And I should not see "Uploaded custom string" in the "page-header" "region"
|
||||
And I should see "Another Uploaded string" in the "page-header" "region"
|
||||
And I should see "Custom string example"
|
||||
And I should not see "Uploaded custom string"
|
||||
And I should see "Another Uploaded string"
|
||||
|
||||
@javascript
|
||||
Scenario: Import all strings
|
||||
@@ -76,9 +76,9 @@ Feature: Within a moodle instance, an administrator should be able to import lan
|
||||
And I should see "String core/nonexistentinvetedstring not found."
|
||||
And I click on "Continue" "button"
|
||||
And I should see "There are 2 modified strings."
|
||||
And I should not see "Uploaded custom string" in the "page-header" "region"
|
||||
And I should not see "Uploaded custom string"
|
||||
And I click on "Save strings to language pack" "button"
|
||||
And I click on "Continue" "button"
|
||||
And I should not see "Custom string example" in the "page-header" "region"
|
||||
And I should see "Uploaded custom string" in the "page-header" "region"
|
||||
And I should see "Another Uploaded string" in the "page-header" "region"
|
||||
And I should not see "Custom string example"
|
||||
And I should see "Uploaded custom string"
|
||||
And I should see "Another Uploaded string"
|
||||
|
||||
@@ -56,7 +56,7 @@ Feature: Manage plearning plan
|
||||
And I set the field "Select cohorts to sync" to "cohort plan"
|
||||
When I click on "Add cohorts" "button"
|
||||
Then I should see "2 learning plans were created."
|
||||
And I follow "Learning plan templates"
|
||||
And I navigate to "Competencies > Learning plan templates" in site administration
|
||||
And I click on ".template-userplans" "css_element" in the "Science template cohort" "table_row"
|
||||
And I should see "Student 1"
|
||||
And I should see "Student 2"
|
||||
|
||||
@@ -149,7 +149,6 @@ Feature: Manage plan workflow
|
||||
And I follow "User 1"
|
||||
And I follow "Learning plans"
|
||||
And I should see "List of learning plans"
|
||||
And I follow "Learning plans"
|
||||
When I click on "Send back to draft" of edit menu in the "Test-Plan3" row
|
||||
And I follow "Test-Plan4"
|
||||
And I follow "Send back to draft"
|
||||
@@ -168,7 +167,6 @@ Feature: Manage plan workflow
|
||||
And I follow "User 1"
|
||||
And I follow "Learning plans"
|
||||
And I should see "List of learning plans"
|
||||
And I follow "Learning plans"
|
||||
When I click on "Complete this learning plan" of edit menu in the "Test-Plan3" row
|
||||
And I click on "Complete this learning plan" "button" in the "Confirm" "dialogue"
|
||||
And I wait until the page is ready
|
||||
@@ -190,7 +188,6 @@ Feature: Manage plan workflow
|
||||
And I follow "User 1"
|
||||
And I follow "Learning plans"
|
||||
And I should see "List of learning plans"
|
||||
And I follow "Learning plans"
|
||||
When I click on "Reopen this learning plan" of edit menu in the "Test-Plan3" row
|
||||
And I click on "Reopen this learning plan" "button" in the "Confirm" "dialogue"
|
||||
And I follow "Test-Plan4"
|
||||
|
||||
@@ -24,11 +24,11 @@ Feature: Basic OAuth2 functionality
|
||||
And I click on "Configure endpoints" "link" in the "Testing service" "table_row"
|
||||
And I should see "https://accounts.google.com/.well-known/openid-configuration" in the "discovery_endpoint" "table_row"
|
||||
And I should see "authorization_endpoint"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Configure user field mappings" "link" in the "Testing service" "table_row"
|
||||
And I should see "firstname" in the "given_name" "table_row"
|
||||
And I should see "middlename" in the "middle_name" "table_row"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Edit" "link" in the "Testing service" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Name | Testing service modified |
|
||||
@@ -57,10 +57,10 @@ Feature: Basic OAuth2 functionality
|
||||
And I click on "Configure endpoints" "link" in the "Testing service" "table_row"
|
||||
And I should see "authorization_endpoint"
|
||||
And I should not see "discovery_endpoint"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Configure user field mappings" "link" in the "Testing service" "table_row"
|
||||
And I should see "firstname" in the "givenName" "table_row"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Edit" "link" in the "Testing service" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Name | Testing service modified |
|
||||
@@ -89,10 +89,10 @@ Feature: Basic OAuth2 functionality
|
||||
And I click on "Configure endpoints" "link" in the "Testing service" "table_row"
|
||||
And I should see "authorization_endpoint"
|
||||
And I should not see "discovery_endpoint"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Configure user field mappings" "link" in the "Testing service" "table_row"
|
||||
And I should see "firstname" in the "first_name" "table_row"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Edit" "link" in the "Testing service" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Name | Testing service modified |
|
||||
@@ -126,10 +126,10 @@ Feature: Basic OAuth2 functionality
|
||||
And I click on "Configure endpoints" "link" in the "Testing service" "table_row"
|
||||
And I should see "authorization_endpoint"
|
||||
And I should not see "discovery_endpoint"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Configure user field mappings" "link" in the "Testing service" "table_row"
|
||||
And I should see "username" in the "ocs-data-id" "table_row"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Edit" "link" in the "Testing service" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Name | Testing service modified |
|
||||
@@ -159,11 +159,11 @@ Feature: Basic OAuth2 functionality
|
||||
And I click on "Configure endpoints" "link" in the "Open Badges" "table_row"
|
||||
And I should see "https://dc.imsglobal.org/.well-known/badgeconnect.json" in the "discovery_endpoint" "table_row"
|
||||
And I should see "authorization_endpoint"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Configure user field mappings" "link" in the "Open Badges" "table_row"
|
||||
And I should not see "given_name"
|
||||
And I should not see "middle_name"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Edit" "link" in the "Open Badges" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Name | IMS Global |
|
||||
@@ -194,11 +194,11 @@ Feature: Basic OAuth2 functionality
|
||||
And I click on "Configure endpoints" "link" in the "Google custom" "table_row"
|
||||
And I should see "https://accounts.google.com/.well-known/openid-configuration" in the "discovery_endpoint" "table_row"
|
||||
And I should see "authorization_endpoint"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Configure user field mappings" "link" in the "Google custom" "table_row"
|
||||
And I should see "firstname" in the "given_name" "table_row"
|
||||
And I should see "middlename" in the "middle_name" "table_row"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Edit" "link" in the "Google custom" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Name | Google custom modified |
|
||||
@@ -227,11 +227,11 @@ Feature: Basic OAuth2 functionality
|
||||
And I should see "-" in the "Invalid custom service" "table_row"
|
||||
And I click on "Configure endpoints" "link" in the "Invalid custom service" "table_row"
|
||||
And I should not see "discovery_endpoint"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Configure user field mappings" "link" in the "Invalid custom service" "table_row"
|
||||
And I should not see "given_name"
|
||||
And I should not see "middle_name"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Edit" "link" in the "Invalid custom service" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Name | Valid custom service |
|
||||
@@ -268,11 +268,11 @@ Feature: Basic OAuth2 functionality
|
||||
And I should see "-" in the "Empty custom service" "table_row"
|
||||
And I click on "Configure endpoints" "link" in the "Empty custom service" "table_row"
|
||||
And I should not see "discovery_endpoint"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Configure user field mappings" "link" in the "Empty custom service" "table_row"
|
||||
And I should not see "given_name"
|
||||
And I should not see "middle_name"
|
||||
And I follow "OAuth 2 services"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I click on "Edit" "link" in the "Empty custom service" "table_row"
|
||||
# Check it works as expected too without slash at the end of the service base URL.
|
||||
And I set the following fields to these values:
|
||||
|
||||
@@ -93,7 +93,7 @@ Feature: Manage analytics models
|
||||
And I click on "View" "link"
|
||||
And I should see "Log extra info"
|
||||
And I click on "Close" "button"
|
||||
And I click on "Analytics models" "link"
|
||||
And I navigate to "Analytics > Analytics models" in site administration
|
||||
# Execute scheduled analysis
|
||||
And I open the action menu in "Students at risk of not meeting the course completion conditions" "table_row"
|
||||
And I choose "Execute scheduled analysis" in the open action menu
|
||||
|
||||
+2
-2
@@ -61,7 +61,7 @@ $PAGE->set_context($context);
|
||||
$PAGE->set_url($currenturl);
|
||||
$PAGE->set_heading($badge->name);
|
||||
$PAGE->set_title($badge->name);
|
||||
$PAGE->navbar->add($badge->name);
|
||||
$PAGE->navbar->add($badge->name, $currenturl);
|
||||
|
||||
$output = $PAGE->get_renderer('core', 'badges');
|
||||
$msg = optional_param('msg', '', PARAM_TEXT);
|
||||
@@ -105,4 +105,4 @@ if ($badge->has_criteria()) {
|
||||
echo $OUTPUT->box(get_string('addcriteriatext', 'badges'));
|
||||
}
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -44,7 +44,7 @@ Feature: Add badges to the system
|
||||
And I should see "Issuer details"
|
||||
And I should see "Test Badge Site"
|
||||
And I should see "testuser@example.com"
|
||||
And I follow "Manage badges"
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I should see "Number of badges available: 1"
|
||||
And I should not see "There are no badges available."
|
||||
|
||||
@@ -61,7 +61,7 @@ Feature: Add badges to the system
|
||||
And I upload "badges/tests/behat/badge.png" file to "Image" filemanager
|
||||
And I press "Create badge"
|
||||
And I wait until the page is ready
|
||||
And I follow "Manage badges"
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I should see "Number of badges available: 1"
|
||||
And I press "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
@@ -157,7 +157,7 @@ Feature: Add badges to the system
|
||||
And I should see "Related badges (0)"
|
||||
And I should see "Alignments (0)"
|
||||
And I should not see "Create badge"
|
||||
And I follow "Manage badges"
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I should see "Number of badges available: 1"
|
||||
And I should not see "There are no badges available."
|
||||
# See buttons from the "Site badges" page.
|
||||
|
||||
@@ -21,7 +21,6 @@ Feature: Award badges
|
||||
And I am on "Course 1" course homepage
|
||||
# Create course badge 1.
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge 1 |
|
||||
| Description | Course badge 1 description |
|
||||
@@ -38,7 +37,6 @@ Feature: Award badges
|
||||
# Badge #2
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge 2 |
|
||||
| Description | Course badge 2 description |
|
||||
@@ -57,6 +55,7 @@ Feature: Award badges
|
||||
# Award course badge 1 to student 1.
|
||||
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
|
||||
When I press "Award badge"
|
||||
And I follow "Manage badges"
|
||||
And I follow "Course Badge 1"
|
||||
And I follow "Recipients (1)"
|
||||
Then I should see "Recipients (1)"
|
||||
@@ -149,6 +148,7 @@ Feature: Award badges
|
||||
And I press "Award badge"
|
||||
And I set the field "potentialrecipients[]" to "student 1 (student1@example.com)"
|
||||
And I press "Award badge"
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
When I follow "Site Badge"
|
||||
Then I should see "Recipients (2)"
|
||||
And I log out
|
||||
@@ -174,7 +174,6 @@ Feature: Award badges
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge |
|
||||
| Description | Course badge description |
|
||||
@@ -191,6 +190,7 @@ Feature: Award badges
|
||||
And I press "Award badge"
|
||||
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
|
||||
When I press "Award badge"
|
||||
And I follow "Manage badges"
|
||||
And I follow "Course Badge"
|
||||
Then I should see "Recipients (2)"
|
||||
And I log out
|
||||
@@ -225,7 +225,6 @@ Feature: Award badges
|
||||
| id_completion | 1 |
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge |
|
||||
| Description | Course badge description |
|
||||
@@ -279,7 +278,6 @@ Feature: Award badges
|
||||
And I press "Save changes"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge |
|
||||
| Description | Course badge description |
|
||||
@@ -328,7 +326,6 @@ Feature: Award badges
|
||||
And I am on "Course 1" course homepage
|
||||
# Create course badge 1.
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge 1 |
|
||||
| Description | Course badge description |
|
||||
@@ -347,13 +344,13 @@ Feature: Award badges
|
||||
# Award course badge 1 to student 1.
|
||||
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
|
||||
When I press "Award badge"
|
||||
And I follow "Manage badges"
|
||||
And I follow "Course Badge 1"
|
||||
And I follow "Recipients (1)"
|
||||
Then I should see "Recipients (1)"
|
||||
# Add course badge 2.
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge 2 |
|
||||
| Description | Course badge description |
|
||||
@@ -372,6 +369,7 @@ Feature: Award badges
|
||||
# Award course badge 2 to student 2.
|
||||
And I set the field "potentialrecipients[]" to "Student 2 (student2@example.com)"
|
||||
When I press "Award badge"
|
||||
And I follow "Manage badges"
|
||||
And I follow "Course Badge 2"
|
||||
And I follow "Recipients (1)"
|
||||
Then I should see "Recipients (1)"
|
||||
@@ -409,7 +407,6 @@ Feature: Award badges
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge |
|
||||
| Description | Course badge description |
|
||||
@@ -426,6 +423,7 @@ Feature: Award badges
|
||||
And I press "Award badge"
|
||||
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
|
||||
When I press "Award badge"
|
||||
And I follow "Manage badges"
|
||||
And I follow "Course Badge"
|
||||
Then I should see "Recipients (2)"
|
||||
And I follow "Recipients (2)"
|
||||
@@ -434,5 +432,6 @@ Feature: Award badges
|
||||
And I press "Revoke badge"
|
||||
And I set the field "existingrecipients[]" to "Student 1 (student1@example.com)"
|
||||
When I press "Revoke badge"
|
||||
And I follow "Manage badges"
|
||||
And I follow "Course Badge"
|
||||
Then I should see "Recipients (0)"
|
||||
|
||||
@@ -37,7 +37,6 @@ Feature: Award badges with separate groups
|
||||
And I set the field "Group mode" to "Separate groups"
|
||||
And I press "Save and display"
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge |
|
||||
| Description | Course badge description |
|
||||
@@ -59,7 +58,6 @@ Feature: Award badges with separate groups
|
||||
When I log in as "teacher2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Manage badges" in current page administration
|
||||
And I follow "Manage badges"
|
||||
And I follow "Course Badge"
|
||||
And I press "Award badge"
|
||||
And I set the field "role" to "Non-editing teacher"
|
||||
@@ -71,6 +69,7 @@ Feature: Award badges with separate groups
|
||||
# Non-editing teacher can award the badge
|
||||
And I set the field "potentialrecipients[]" to "Student 2 (student2@example.com)"
|
||||
And I press "Award badge"
|
||||
And I follow "Manage badges"
|
||||
And I follow "Course Badge"
|
||||
And I should see "Recipients (1)"
|
||||
And I log out
|
||||
@@ -85,7 +84,6 @@ Feature: Award badges with separate groups
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Groups" in current page administration
|
||||
And I follow "Groups"
|
||||
And I set the field "groups" to "Class B (2)"
|
||||
And I press "Add/remove users"
|
||||
And I set the field "addselect" to "Teacher 2 (teacher2@example.com)"
|
||||
@@ -94,7 +92,6 @@ Feature: Award badges with separate groups
|
||||
When I log in as "teacher2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Manage badges" in current page administration
|
||||
And I follow "Manage badges"
|
||||
And I follow "Course Badge"
|
||||
And I press "Award badge"
|
||||
And I set the field "role" to "Non-editing teacher"
|
||||
@@ -112,7 +109,6 @@ Feature: Award badges with separate groups
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Users > Groups" in current page administration
|
||||
And I follow "Groups"
|
||||
And I set the field "groups" to "Class A (2)"
|
||||
And I press "Add/remove users"
|
||||
And I set the field "removeselect" to "Teacher 2 (teacher2@example.com)"
|
||||
@@ -122,7 +118,6 @@ Feature: Award badges with separate groups
|
||||
When I log in as "teacher2"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Manage badges" in current page administration
|
||||
And I follow "Manage badges"
|
||||
And I follow "Course Badge"
|
||||
And I press "Award badge"
|
||||
# Teacher 2 shouldn't be able to go further
|
||||
|
||||
@@ -36,7 +36,6 @@ Feature: Award badges based on activity completion
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge |
|
||||
| Description | Course badge description |
|
||||
|
||||
@@ -152,6 +152,7 @@ Feature: Award badges based on cohort
|
||||
And I press "Award badge"
|
||||
And I set the field "potentialrecipients[]" to "Second User (second@example.com)"
|
||||
And I press "Award badge"
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Site Badge"
|
||||
Then I should see "Recipients (1)"
|
||||
And I log out
|
||||
@@ -203,6 +204,7 @@ Feature: Award badges based on cohort
|
||||
And I press "Award badge"
|
||||
And I set the field "potentialrecipients[]" to "Second User (second@example.com)"
|
||||
And I press "Award badge"
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Site Badge"
|
||||
Then I should see "Recipients (2)"
|
||||
And I log out
|
||||
@@ -259,6 +261,7 @@ Feature: Award badges based on cohort
|
||||
And I press "Award badge"
|
||||
And I set the field "potentialrecipients[]" to "Second User (second@example.com)"
|
||||
And I press "Award badge"
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Site Badge"
|
||||
Then I should see "Recipients (2)"
|
||||
And I log out
|
||||
@@ -317,6 +320,7 @@ Feature: Award badges based on cohort
|
||||
And I press "Award badge"
|
||||
And I set the field "potentialrecipients[]" to "Second User (second@example.com)"
|
||||
And I press "Award badge"
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Site Badge"
|
||||
Then I should see "Recipients (1)"
|
||||
And I log out
|
||||
|
||||
@@ -40,7 +40,6 @@ Feature: Award badges based on competency completion
|
||||
# Add a badge to the course
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge |
|
||||
| Description | Course badge description |
|
||||
|
||||
@@ -22,7 +22,6 @@ Feature: Test role visibility for the badge administration page
|
||||
Given I log in as "manager1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge |
|
||||
| Description | Course badge description |
|
||||
@@ -37,7 +36,6 @@ Feature: Test role visibility for the badge administration page
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "Badges > Add a new badge" in current page administration
|
||||
And I follow "Add a new badge"
|
||||
And I set the following fields to these values:
|
||||
| Name | Course Badge |
|
||||
| Description | Course badge description |
|
||||
|
||||
@@ -35,6 +35,7 @@ Feature: Display badges
|
||||
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
|
||||
And I press "Award badge"
|
||||
# Check badge details are displayed.
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Testing system badge"
|
||||
And I follow "Recipients (1)"
|
||||
When I click on "View issued badge" "link" in the "Student 1" "table_row"
|
||||
@@ -59,6 +60,7 @@ Feature: Display badges
|
||||
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
|
||||
And I press "Award badge"
|
||||
# Check badge details are displayed.
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Testing system badge"
|
||||
And I follow "Recipients (1)"
|
||||
When I click on "View issued badge" "link" in the "Student 1" "table_row"
|
||||
@@ -107,6 +109,7 @@ Feature: Display badges
|
||||
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
|
||||
And I press "Award badge"
|
||||
# Check "Expires" date is displayed.
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Testing system badge"
|
||||
And I follow "Recipients (1)"
|
||||
And I click on "View issued badge" "link" in the "Student 1" "table_row"
|
||||
@@ -130,6 +133,7 @@ Feature: Display badges
|
||||
# Wait 1 second to guarantee the badge is expired.
|
||||
And I wait "1" seconds
|
||||
# Check "Expired" date is displayed.
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Testing system badge"
|
||||
And I follow "Recipients (1)"
|
||||
And I click on "View issued badge" "link" in the "Student 1" "table_row"
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_block\local\views;
|
||||
|
||||
/**
|
||||
* Class secondary
|
||||
*
|
||||
* @package core_block
|
||||
* @category navigation
|
||||
*/
|
||||
class secondary extends \core\navigation\views\secondary {
|
||||
/**
|
||||
* Blocks don't require secondary navs as they can be accessed from multiple places and in different contexts.
|
||||
*/
|
||||
public function initialise(): void {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -59,5 +59,4 @@ Feature: Block appearances
|
||||
| Visible | No |
|
||||
And I press "Save changes"
|
||||
And I press "Turn editing off"
|
||||
And I follow "Test book name"
|
||||
Then I should not see "Comments"
|
||||
|
||||
@@ -33,6 +33,10 @@ Feature: Add cohorts of users
|
||||
Then the following should exist in the "generaltable" table:
|
||||
| Name | Cohort ID | Description |
|
||||
| My new cohort | mynewcohort | My new cohort is cool |
|
||||
And I should see "Test cohort name"
|
||||
And I should see "333"
|
||||
And I should see "Test cohort description"
|
||||
And I should see "Created manually"
|
||||
|
||||
@javascript
|
||||
Scenario: Add users to a cohort selecting them from the system users list
|
||||
@@ -76,8 +80,7 @@ Feature: Add cohorts of users
|
||||
|
||||
@javascript
|
||||
Scenario: Add users to a cohort using a bulk user action
|
||||
When I follow "Accounts"
|
||||
And I follow "Bulk user actions"
|
||||
When I navigate to "Users > Accounts > Bulk user actions" in site administration
|
||||
And I set the field "Available" to "Third User"
|
||||
And I press "Add to selection"
|
||||
And I set the field "Available" to "Forth User"
|
||||
@@ -86,8 +89,7 @@ Feature: Add cohorts of users
|
||||
And I press "Go"
|
||||
And I set the field "Cohort" to "Test cohort name [333]"
|
||||
And I press "Add to cohort"
|
||||
And I follow "Accounts"
|
||||
And I follow "Cohorts"
|
||||
And I navigate to "Users > Accounts > Cohorts" in site administration
|
||||
Then I should see "2" in the "#cohorts" "css_element"
|
||||
And I click on "Assign" "link" in the "Test cohort name" "table_row"
|
||||
And the "Current users" select box should contain "Third User (third@example.com)"
|
||||
@@ -96,9 +98,9 @@ Feature: Add cohorts of users
|
||||
|
||||
@javascript
|
||||
Scenario: Edit cohort name in-place
|
||||
When I follow "Cohorts"
|
||||
When I navigate to "Users > Accounts > Cohorts" in site administration
|
||||
And I set the field "Edit cohort name" to "Students cohort"
|
||||
Then I should not see "Test cohort name"
|
||||
And I should see "Students cohort"
|
||||
And I follow "Cohorts"
|
||||
And I navigate to "Users > Accounts > Cohorts" in site administration
|
||||
And I should see "Students cohort"
|
||||
|
||||
@@ -91,7 +91,7 @@ Feature: Restricting access to course lists
|
||||
And I should see "Biology"
|
||||
And I should not see "Humanities"
|
||||
And I click on "Courses" "link" in the "Navigation" "block"
|
||||
And "category" "text" should not exist in the ".breadcrumb" "css_element"
|
||||
# And "category" "text" should not exist in the ".breadcrumb" "css_element"
|
||||
And I should see "Science category"
|
||||
And I should see "English category"
|
||||
And I should not see "Other category"
|
||||
|
||||
@@ -81,12 +81,12 @@ Feature: Course activity controls works as expected
|
||||
|
||||
Examples:
|
||||
| courseformat | coursedisplay | targetpage | should_see_other_sections | should_see_other_sections_following_block_sections_links | belowpage |
|
||||
| topics | 0 | "Course 1" | should | should | "Topic 2" |
|
||||
| topics | 0 | "C1" | should | should | "Topic 2" |
|
||||
| topics | 1 | "Topic 1" | should not | should not | "Topic 2" |
|
||||
| topics | 1 | "Course 1" | should | should not | "Topic 2" |
|
||||
| weeks | 0 | "Course 1" | should | should | "8 January - 14 January" |
|
||||
| topics | 1 | "C1" | should | should not | "Topic 2" |
|
||||
| weeks | 0 | "C1" | should | should | "8 January - 14 January" |
|
||||
| weeks | 1 | "1 January - 7 January" | should not | should not | "8 January - 14 January" |
|
||||
| weeks | 1 | "Course 1" | should | should not | "8 January - 14 January" |
|
||||
| weeks | 1 | "C1" | should | should not | "8 January - 14 January" |
|
||||
|
||||
Scenario Outline: General activities course controls using topics and weeks formats, and paged mode and not paged mode works as expected
|
||||
Given the following "users" exist:
|
||||
@@ -146,9 +146,9 @@ Feature: Course activity controls works as expected
|
||||
|
||||
Examples:
|
||||
| courseformat | coursedisplay | targetpage | should_see_other_sections | should_see_other_sections_following_block_sections_links | belowpage |
|
||||
| topics | 0 | "Course 1" | should | should | "Topic 2" |
|
||||
| topics | 0 | "C1" | should | should | "Topic 2" |
|
||||
| topics | 1 | "Topic 1" | should not | should not | "Topic 2" |
|
||||
| topics | 1 | "Course 1" | should | should not | "Topic 2" |
|
||||
| weeks | 0 | "Course 1" | should | should | "8 January - 14 January" |
|
||||
| topics | 1 | "C1" | should | should not | "Topic 2" |
|
||||
| weeks | 0 | "C1" | should | should | "8 January - 14 January" |
|
||||
| weeks | 1 | "1 January - 7 January" | should not | should not | "8 January - 14 January" |
|
||||
| weeks | 1 | "Course 1" | should | should not | "8 January - 14 January" |
|
||||
| weeks | 1 | "C1" | should | should not | "8 January - 14 January" |
|
||||
|
||||
@@ -23,7 +23,6 @@ Feature: Edit course settings
|
||||
| Course short name | Edited course shortname |
|
||||
| Course summary | Edited course summary |
|
||||
And I press "Save and display"
|
||||
And I follow "Edited course fullname"
|
||||
Then I should not see "Course 1"
|
||||
And I should not see "C1"
|
||||
And I should see "Edited course fullname"
|
||||
|
||||
@@ -85,7 +85,7 @@ class behat_grading extends behat_base {
|
||||
// Step to access the user grade page from the grading page.
|
||||
$gradetext = get_string('gradeverb');
|
||||
|
||||
$this->execute("behat_general::i_click_on_in_the", [$this->escape($activityname), 'link', 'page', 'region']);
|
||||
$this->execute('behat_navigation::go_to_breadcrumb_location', $this->escape($activityname));
|
||||
|
||||
$this->execute('behat_navigation::i_navigate_to_in_current_page_administration',
|
||||
get_string('viewgrading', 'mod_assign'));
|
||||
|
||||
@@ -1006,6 +1006,7 @@ $string['hits'] = 'Hits';
|
||||
$string['hitsoncourse'] = 'Hits on {$a->coursename} by {$a->username}';
|
||||
$string['hitsoncoursetoday'] = 'Today\'s hits on {$a->coursename} by {$a->username}';
|
||||
$string['home'] = 'Home';
|
||||
$string['homeheader'] = 'Home menu';
|
||||
$string['hour'] = 'hour';
|
||||
$string['hours'] = 'hours';
|
||||
$string['howtomakethemes'] = 'How to make new themes';
|
||||
@@ -1140,6 +1141,7 @@ $string['langltr'] = 'Language direction left-to-right';
|
||||
$string['langrtl'] = 'Language direction right-to-left';
|
||||
$string['language'] = 'Language';
|
||||
$string['languagegood'] = 'This language pack is up-to-date! :-)';
|
||||
$string['languageselector'] = 'Language selector';
|
||||
$string['last'] = 'Last';
|
||||
$string['lastaccess'] = 'Last access';
|
||||
$string['lastcourseaccess'] = 'Last access to course';
|
||||
@@ -1350,6 +1352,7 @@ $string['movefilestohere'] = 'Move files to here';
|
||||
$string['movefull'] = 'Move {$a} to this location';
|
||||
$string['movehere'] = 'Move to here';
|
||||
$string['moveleft'] = 'Move left';
|
||||
$string['moremenu'] = 'More';
|
||||
$string['moveright'] = 'Move right';
|
||||
$string['movesection'] = 'Move section {$a}';
|
||||
$string['moveselectedcategoriesto'] = 'Move selected categories to';
|
||||
@@ -1735,6 +1738,7 @@ $string['rename'] = 'Rename';
|
||||
$string['renamefileto'] = 'Rename <b>{$a}</b> to';
|
||||
$string['report'] = 'Report';
|
||||
$string['reports'] = 'Reports';
|
||||
$string['reporttype'] = 'Report type';
|
||||
$string['repositories'] = 'Repositories';
|
||||
$string['requestcourse'] = 'Request a course';
|
||||
$string['requestedby'] = 'Requested by';
|
||||
@@ -2214,6 +2218,7 @@ $string['userdetails'] = 'User details';
|
||||
$string['userfiles'] = 'User files';
|
||||
$string['userlist'] = 'User list';
|
||||
$string['usermenu'] = 'User menu';
|
||||
$string['usermenugoback'] = 'Go back to user menu';
|
||||
$string['username'] = 'Username';
|
||||
$string['usernameemail'] = 'Username / email';
|
||||
$string['usernameemailmatch'] = 'The username and email address do not relate to the same user';
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
define ("core/menu_navigation",["exports","core/key_codes"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var c={menuitem:"[role=\"menuitem\"]",menu:"[role=\"menu\"]"},d=null,e=function(a,b){if(null!==a){return a}else{return b}},f=function(a){var e=a.srcElement,f=a.currentTarget.firstElementChild,k=j(a.currentTarget);if(e.classList.contains("dropdown-item")){if(a.keyCode===b.arrowRight||a.keyCode===b.arrowLeft){a.preventDefault();if(null!==d){d.parentElement.click()}}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();Array.prototype.forEach.call(e.closest(".dropdown-menu").children,function(a){a.querySelector(c.menuitem).classList.remove("active");a.setAttribute("aria-current","false")});if(!e.parentElement.classList.contains("dropdown")){e.click()}}}else{if(a.keyCode===b.arrowRight){a.preventDefault();g(e,f)}if(a.keyCode===b.arrowLeft){a.preventDefault();h(e,k)}if(a.keyCode===b.arrowUp||a.keyCode===b.arrowDown){d=e;a.preventDefault()}if(a.keyCode===b.home){a.preventDefault();e.setAttribute("aria-current","false");i(f)}if(a.keyCode===b.end){a.preventDefault();e.currentNode.setAttribute("aria-current","false");i(k)}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();if(!e.parentElement.classList.contains("dropdown")){e.click()}}}};a.default=function(a){a.removeEventListener("keydown",f);a.addEventListener("keydown",f)};var g=function(a,b){var d=a.parentElement.nextElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);a.setAttribute("aria-current","false");g.setAttribute("aria-current","true");g.focus()},h=function(a,b){var d=a.parentElement.previousElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);a.setAttribute("aria-current","false");g.setAttribute("aria-current","true");g.focus()},i=function(a){a.querySelector(c.menuitem).focus();a.querySelector(c.menuitem).setAttribute("aria-current","true")},j=function(a){var b=a.lastElementChild;if(!b.classList.contains("d-none")){return a.lastElementChild}else{var c=Array.prototype.map.call(a.children,function(a){return a}).reverse(),d=c.filter(function(a){if(!a.classList.contains("d-none")){return a}});if(0!==d.length){return d[0]}else{return a.firstElementChild}}};return a.default});
|
||||
//# sourceMappingURL=menu_navigation.min.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
define ("core/moremenu",["exports","jquery","core/menu_navigation"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}var f={regions:{moredropdown:"[data-region=\"moredropdown\"]",morebutton:"[data-region=\"morebutton\"]"},classes:{dropdownitem:"dropdown-item",dropdownmoremenu:"dropdownmoremenu",dropdowntoggle:"dropdown-toggle",hidden:"d-none",active:"active",nav:"nav",navlink:"nav-link",observed:"observed"},attributes:{menu:"[role=\"menu\"]"}},g=function(a){var b=a.parentNode.offsetHeight+1,c=a.querySelector(f.regions.moredropdown),d=a.querySelector(f.regions.morebutton);if(a.offsetHeight>b){d.classList.remove(f.classes.hidden);var e=Array.from(a.children).reverse();e.forEach(function(c){if(!c.classList.contains(f.classes.dropdownmoremenu)){if(a.offsetHeight>b){var d=a.removeChild(c);h(a,d,!0)}}})}else{if("children"in c){var j=Array.from(c.children);j.forEach(function(d){if(a.offsetHeight<b&&"true"!==d.dataset.forceintomoremenu){var e=c.removeChild(d);i(a,e)}});if(0===j.length){d.classList.add(f.classes.hidden)}}if(a.offsetHeight>b){g(a)}}a.parentNode.classList.add(f.classes.observed)},h=function(a,b){var c=2<arguments.length&&arguments[2]!==void 0?arguments[2]:!1,d=a.querySelector(f.regions.moredropdown),e=a.querySelector("."+f.classes.dropdowntoggle),g=b.querySelector("."+f.classes.navlink);if(g.classList.contains(f.classes.active)){e.classList.add(f.classes.active)}g.classList.remove(f.classes.navlink);g.classList.add(f.classes.dropdownitem);if(c){d.prepend(b)}else{d.append(b)}},i=function(a,b){var c=a.querySelector(f.regions.morebutton),d=a.querySelector("."+f.classes.dropdowntoggle),e=b.querySelector("."+f.classes.dropdownitem);if(e.classList.contains(f.classes.active)){d.classList.remove(f.classes.active)}e.classList.remove(f.classes.dropdownitem);e.classList.add(f.classes.navlink);a.insertBefore(b,c)},j=function(a){a.firstElementChild.querySelector("[role=\"menuitem\"]").setAttribute("tabindex","0");if("children"in a){var d=a.querySelector(f.regions.morebutton),e=Array.from(a.children);e.forEach(function(b){if(!b.classList.contains(f.classes.dropdownmoremenu)&&"true"===b.dataset.forceintomoremenu){h(a,b,!1);if(d.classList.contains(f.classes.hidden)){d.classList.remove(f.classes.hidden)}}})}g(a);(0,c.default)(a);window.addEventListener("resize",function(){g(a);(0,c.default)(a)});var i=function(a){var b=a.target.parentNode.querySelector(f.attributes.menu);if(b){b.classList.toggle("show")}a.stopPropagation()};(0,b.default)("."+f.classes.dropdownmoremenu).on("show.bs.dropdown",function(){var b=a.querySelector(f.regions.moredropdown);b.querySelectorAll(".dropdown").forEach(function(a){a.removeEventListener("click",i,!0);a.addEventListener("click",i,!0)})})};a.default=j;return a.default});
|
||||
//# sourceMappingURL=moremenu.min.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
define ("core/usermenu",["exports","jquery","core/key_codes"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var d={userMenu:".usermenu",userMenuCarousel:".usermenu #usermenu-carousel",userMenuCarouselItem:".usermenu #usermenu-carousel .carousel-item",userMenuCarouselItemActive:".usermenu #usermenu-carousel .carousel-item.active",userMenuCarouselNavigationLink:".usermenu #usermenu-carousel .carousel-navigation-link"},e=function(){var a=document.querySelector(d.userMenu);(0,b.default)(d.userMenu).on("shown.bs.dropdown",function(){var b=document.querySelector(d.userMenuCarouselItemActive);b.focus();a.querySelectorAll(d.userMenuCarouselItem).forEach(function(a){if(!a.classList.contains("active")){a.style.width=b.offsetWidth+"px";a.style.height=b.offsetHeight+"px"}})});a.addEventListener("click",function(a){if(a.target.matches(d.userMenuCarouselNavigationLink)){f(a)}});a.addEventListener("keydown",function(a){if((a.keyCode===c.space||a.keyCode===c.enter)&&a.target.matches(d.userMenuCarouselNavigationLink)){a.preventDefault();f(a)}});var f=function(c){c.stopPropagation();var e=c.target.dataset.carouselTargetId,f=a.querySelector("#"+e),g=Array.from(f.parentNode.children).indexOf(f);(0,b.default)(d.userMenuCarousel).carousel(g)};(0,b.default)(d.userMenu).on("hide.bs.dropdown",function(){(0,b.default)(d.userMenuCarousel).carousel(0)});(0,b.default)(d.userMenuCarousel).on("slid.bs.carousel",function(){var b=a.querySelector(d.userMenuCarouselItemActive);b.focus()})};a.default={init:function init(){e()}};return a.default});
|
||||
//# sourceMappingURL=usermenu.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,201 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Keyboard initialization for a given html node.
|
||||
*
|
||||
* @module core/keyboard_navigation
|
||||
* @copyright 2021 Moodle
|
||||
* @author Mathew May <mathew.solutions>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {space, enter, arrowRight, arrowLeft, arrowDown, arrowUp, home, end} from 'core/key_codes';
|
||||
|
||||
const SELECTORS = {
|
||||
'menuitem': '[role="menuitem"]',
|
||||
'menu': '[role="menu"]'
|
||||
};
|
||||
|
||||
let openDropdownNode = null;
|
||||
|
||||
/**
|
||||
* Small helper function to check if a given node is null or not.
|
||||
*
|
||||
* @param {HTMLElement|null} item The node that we want to compare.
|
||||
* @param {HTMLElement} fallback Either the first node or final node that can be focused on.
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
const clickErrorHandler = (item, fallback) => {
|
||||
if (item !== null) {
|
||||
return item;
|
||||
} else {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Defined event handling so we can remove listeners on nodes on resize etc.
|
||||
*
|
||||
* @param {event} e The triggering element and key presses etc.
|
||||
*/
|
||||
const listenerEvents = e => {
|
||||
const src = e.srcElement;
|
||||
const firstNode = e.currentTarget.firstElementChild;
|
||||
const lastNode = findUsableLastNode(e.currentTarget);
|
||||
|
||||
// Handling for dropdown escapes.
|
||||
// A bulk of the handling is already done by aria.js just add polish.
|
||||
if (src.classList.contains('dropdown-item')) {
|
||||
if (e.keyCode === arrowRight ||
|
||||
e.keyCode === arrowLeft) {
|
||||
e.preventDefault();
|
||||
if (openDropdownNode !== null) {
|
||||
openDropdownNode.parentElement.click();
|
||||
}
|
||||
}
|
||||
if (e.keyCode === space ||
|
||||
e.keyCode === enter) {
|
||||
e.preventDefault();
|
||||
|
||||
// Remove active class from any other dropdown elements.
|
||||
Array.prototype.forEach.call(src.closest('.dropdown-menu').children, node => {
|
||||
node.querySelector(SELECTORS.menuitem).classList.remove('active');
|
||||
node.setAttribute('aria-current', 'false');
|
||||
});
|
||||
|
||||
if (!src.parentElement.classList.contains('dropdown')) {
|
||||
src.click();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (e.keyCode === arrowRight) {
|
||||
e.preventDefault();
|
||||
setFocusNext(src, firstNode);
|
||||
}
|
||||
if (e.keyCode === arrowLeft) {
|
||||
e.preventDefault();
|
||||
setFocusPrev(src, lastNode);
|
||||
}
|
||||
// Let aria.js handle the dropdowns.
|
||||
if (e.keyCode === arrowUp ||
|
||||
e.keyCode === arrowDown) {
|
||||
openDropdownNode = src;
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.keyCode === home) {
|
||||
e.preventDefault();
|
||||
src.setAttribute('aria-current', 'false');
|
||||
setFocusHomeEnd(firstNode);
|
||||
}
|
||||
if (e.keyCode === end) {
|
||||
e.preventDefault();
|
||||
src.currentNode.setAttribute('aria-current', 'false');
|
||||
setFocusHomeEnd(lastNode);
|
||||
}
|
||||
if (e.keyCode === space ||
|
||||
e.keyCode === enter) {
|
||||
e.preventDefault();
|
||||
// Aria.js handles dropdowns etc.
|
||||
if (!src.parentElement.classList.contains('dropdown')) {
|
||||
src.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The initial entry point that a given module can pass a HTMLElement.
|
||||
*
|
||||
* @param {HTMLElement} elementRoot The menu to add handlers upon.
|
||||
*/
|
||||
export default elementRoot => {
|
||||
elementRoot.removeEventListener('keydown', listenerEvents);
|
||||
elementRoot.addEventListener('keydown', listenerEvents);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the focusing to the next element in the dropdown.
|
||||
*
|
||||
* @param {HTMLElement|null} currentNode The node that we want to take action on.
|
||||
* @param {HTMLElement} firstNode The backup node to focus as a last resort.
|
||||
*/
|
||||
const setFocusNext = (currentNode, firstNode) => {
|
||||
const nextListItem = currentNode.parentElement.nextElementSibling;
|
||||
const nodeToSelect = clickErrorHandler(nextListItem, firstNode);
|
||||
const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem);
|
||||
currentNode.setAttribute('aria-current', 'false');
|
||||
menuItem.setAttribute('aria-current', 'true');
|
||||
menuItem.focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the focusing to the previous element in the dropdown.
|
||||
*
|
||||
* @param {HTMLElement|null} currentNode The node that we want to take action on.
|
||||
* @param {HTMLElement} lastNode The backup node to focus as a last resort.
|
||||
*/
|
||||
const setFocusPrev = (currentNode, lastNode) => {
|
||||
const nextListItem = currentNode.parentElement.previousElementSibling;
|
||||
const nodeToSelect = clickErrorHandler(nextListItem, lastNode);
|
||||
const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem);
|
||||
currentNode.setAttribute('aria-current', 'false');
|
||||
menuItem.setAttribute('aria-current', 'true');
|
||||
menuItem.focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus on either the start or end of a nav list.
|
||||
*
|
||||
* @param {HTMLElement} node The element to focus on.
|
||||
*/
|
||||
const setFocusHomeEnd = node => {
|
||||
node.querySelector(SELECTORS.menuitem).focus();
|
||||
node.querySelector(SELECTORS.menuitem).setAttribute('aria-current', 'true');
|
||||
};
|
||||
|
||||
/**
|
||||
* We need to look within the menu to find a last node we can add focus to.
|
||||
*
|
||||
* @param {HTMLElement} elementRoot Menu to find a final child node within.
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
const findUsableLastNode = elementRoot => {
|
||||
const lastNode = elementRoot.lastElementChild;
|
||||
|
||||
// An example is the more menu existing but hidden on the page for the time being.
|
||||
if (!lastNode.classList.contains('d-none')) {
|
||||
return elementRoot.lastElementChild;
|
||||
} else {
|
||||
// Cast the HTMLCollection & reverse it.
|
||||
const extractedNodes = Array.prototype.map.call(elementRoot.children, node => {
|
||||
return node;
|
||||
}).reverse();
|
||||
|
||||
// Get rid of any nodes we can not set focus on.
|
||||
const nodesToUse = extractedNodes.filter((node => {
|
||||
if (!node.classList.contains('d-none')) {
|
||||
return node;
|
||||
}
|
||||
}));
|
||||
|
||||
// If we find no elements we can set focus on, fall back to the absolute first element.
|
||||
if (nodesToUse.length !== 0) {
|
||||
return nodesToUse[0];
|
||||
} else {
|
||||
return elementRoot.firstElementChild;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,215 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Moves wrapping navigation items into a more menu.
|
||||
*
|
||||
* @module core/moremenu
|
||||
* @copyright 2021 Moodle
|
||||
* @author Bas Brands <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import menu_navigation from "core/menu_navigation";
|
||||
/**
|
||||
* Moremenu selectors.
|
||||
*/
|
||||
const Selectors = {
|
||||
regions: {
|
||||
moredropdown: '[data-region="moredropdown"]',
|
||||
morebutton: '[data-region="morebutton"]'
|
||||
},
|
||||
classes: {
|
||||
dropdownitem: 'dropdown-item',
|
||||
dropdownmoremenu: 'dropdownmoremenu',
|
||||
dropdowntoggle: 'dropdown-toggle',
|
||||
hidden: 'd-none',
|
||||
active: 'active',
|
||||
nav: 'nav',
|
||||
navlink: 'nav-link',
|
||||
observed: 'observed',
|
||||
},
|
||||
attributes: {
|
||||
menu: '[role="menu"]'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Auto Collapse navigation items that wrap into a dropdown menu.
|
||||
*
|
||||
* @param {HTMLElement} menu The navbar container.
|
||||
*/
|
||||
const autoCollapse = menu => {
|
||||
|
||||
const maxHeight = menu.parentNode.offsetHeight + 1;
|
||||
|
||||
const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);
|
||||
const moreButton = menu.querySelector(Selectors.regions.morebutton);
|
||||
|
||||
// If the menu items wrap and the menu height is larger than the height of the
|
||||
// parent then start pushing navlinks into the moreDropdown.
|
||||
if (menu.offsetHeight > maxHeight) {
|
||||
|
||||
moreButton.classList.remove(Selectors.classes.hidden);
|
||||
|
||||
const menuNodes = Array.from(menu.children).reverse();
|
||||
menuNodes.forEach(item => {
|
||||
if (!item.classList.contains(Selectors.classes.dropdownmoremenu)) {
|
||||
// After moving the menu items into the moreDropdown check again
|
||||
// if the menu height is still larger then the height of the parent.
|
||||
if (menu.offsetHeight > maxHeight) {
|
||||
const lastNode = menu.removeChild(item);
|
||||
// Move this node into the more dropdown menu.
|
||||
moveIntoMoreDropdown(menu, lastNode, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If the menu height is smaller than the height of the parent, then try returning navlinks to the menu.
|
||||
|
||||
if ('children' in moreDropdown) {
|
||||
const menuNodes = Array.from(moreDropdown.children);
|
||||
menuNodes.forEach(item => {
|
||||
// Don't move the node to the more menu if it is explicitly defined that
|
||||
// this node should be displayed in the more dropdown menu at all times.
|
||||
if (menu.offsetHeight < maxHeight && item.dataset.forceintomoremenu !== 'true') {
|
||||
const lastNode = moreDropdown.removeChild(item);
|
||||
// Move this node from the more dropdown menu into the main section of the menu.
|
||||
moveOutOfMoreDropdown(menu, lastNode);
|
||||
}
|
||||
});
|
||||
|
||||
// If there are no more menuNodes in the dropdown we can hide the moreButton.
|
||||
if (menuNodes.length === 0) {
|
||||
moreButton.classList.add(Selectors.classes.hidden);
|
||||
}
|
||||
}
|
||||
|
||||
if (menu.offsetHeight > maxHeight) {
|
||||
autoCollapse(menu);
|
||||
}
|
||||
}
|
||||
menu.parentNode.classList.add(Selectors.classes.observed);
|
||||
};
|
||||
|
||||
/**
|
||||
* Move a node into the "more" dropdown menu.
|
||||
*
|
||||
* This method forces a given navigation node to be added and displayed within the "more" dropdown menu.
|
||||
*
|
||||
* @param {HTMLElement} menu The navbar moremenu.
|
||||
* @param {HTMLElement} navNode The navigation node.
|
||||
* @param {boolean} prepend Whether to prepend or append the node to the content in the more dropdown menu.
|
||||
*/
|
||||
const moveIntoMoreDropdown = (menu, navNode, prepend = false) => {
|
||||
const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);
|
||||
const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle);
|
||||
|
||||
const navLink = navNode.querySelector('.' + Selectors.classes.navlink);
|
||||
// If there are navLinks that contain an active link in the moreDropdown
|
||||
// make the dropdownToggle in the moreButton active.
|
||||
if (navLink.classList.contains(Selectors.classes.active)) {
|
||||
dropdownToggle.classList.add(Selectors.classes.active);
|
||||
}
|
||||
|
||||
// Change the styling of the navLink to a dropdownitem and push it into
|
||||
// the moreDropdown.
|
||||
navLink.classList.remove(Selectors.classes.navlink);
|
||||
navLink.classList.add(Selectors.classes.dropdownitem);
|
||||
if (prepend) {
|
||||
moreDropdown.prepend(navNode);
|
||||
} else {
|
||||
moreDropdown.append(navNode);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Move a node out of the "more" dropdown menu.
|
||||
*
|
||||
* This method forces a given node from the "more" dropdown menu to be displayed in the main section of the menu.
|
||||
*
|
||||
* @param {HTMLElement} menu The navbar moremenu.
|
||||
* @param {HTMLElement} navNode The navigation node.
|
||||
*/
|
||||
const moveOutOfMoreDropdown = (menu, navNode) => {
|
||||
const moreButton = menu.querySelector(Selectors.regions.morebutton);
|
||||
const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle);
|
||||
const navLink = navNode.querySelector('.' + Selectors.classes.dropdownitem);
|
||||
|
||||
// Stop displaying the active state on the dropdownToggle if
|
||||
// the active navlink is removed.
|
||||
if (navLink.classList.contains(Selectors.classes.active)) {
|
||||
dropdownToggle.classList.remove(Selectors.classes.active);
|
||||
}
|
||||
navLink.classList.remove(Selectors.classes.dropdownitem);
|
||||
navLink.classList.add(Selectors.classes.navlink);
|
||||
menu.insertBefore(navNode, moreButton);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise the more menus.
|
||||
*
|
||||
* @param {HTMLElement} menu The navbar moremenu.
|
||||
*/
|
||||
export default menu => {
|
||||
menu.firstElementChild.querySelector('[role="menuitem"]').setAttribute('tabindex', '0');
|
||||
// Pre-populate the "more" dropdown menu with navigation nodes which are set to be displayed in this menu
|
||||
// by default at all times.
|
||||
if ('children' in menu) {
|
||||
const moreButton = menu.querySelector(Selectors.regions.morebutton);
|
||||
const menuNodes = Array.from(menu.children);
|
||||
menuNodes.forEach((item) => {
|
||||
if (!item.classList.contains(Selectors.classes.dropdownmoremenu) &&
|
||||
item.dataset.forceintomoremenu === 'true') {
|
||||
// Append this node into the more dropdown menu.
|
||||
moveIntoMoreDropdown(menu, item, false);
|
||||
// After adding the node into the more dropdown menu, make sure that the more dropdown menu button
|
||||
// is displayed.
|
||||
if (moreButton.classList.contains(Selectors.classes.hidden)) {
|
||||
moreButton.classList.remove(Selectors.classes.hidden);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Populate the more dropdown menu with additional nodes if necessary, depending on the current screen size.
|
||||
autoCollapse(menu);
|
||||
menu_navigation(menu);
|
||||
|
||||
// When the screen size changes make sure the menu still fits.
|
||||
window.addEventListener('resize', () => {
|
||||
autoCollapse(menu);
|
||||
menu_navigation(menu);
|
||||
});
|
||||
|
||||
const toggledropdown = e => {
|
||||
const innerMenu = e.target.parentNode.querySelector(Selectors.attributes.menu);
|
||||
if (innerMenu) {
|
||||
innerMenu.classList.toggle('show');
|
||||
}
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
// If there are dropdowns in the MoreMenu, add a new
|
||||
// event listener to show the contents on click and prevent the
|
||||
// moreMenu from closing.
|
||||
$('.' + Selectors.classes.dropdownmoremenu).on('show.bs.dropdown', function() {
|
||||
const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);
|
||||
moreDropdown.querySelectorAll('.dropdown').forEach((dropdown) => {
|
||||
dropdown.removeEventListener('click', toggledropdown, true);
|
||||
dropdown.addEventListener('click', toggledropdown, true);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,127 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Initializes and handles events in the user menu.
|
||||
*
|
||||
* @module core/usermenu
|
||||
* @copyright 2021 Moodle
|
||||
* @author Mihail Geshoski <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import {space, enter} from 'core/key_codes';
|
||||
|
||||
/**
|
||||
* User menu constants.
|
||||
*/
|
||||
const selectors = {
|
||||
userMenu: '.usermenu',
|
||||
userMenuCarousel: '.usermenu #usermenu-carousel',
|
||||
userMenuCarouselItem: '.usermenu #usermenu-carousel .carousel-item',
|
||||
userMenuCarouselItemActive: '.usermenu #usermenu-carousel .carousel-item.active',
|
||||
userMenuCarouselNavigationLink: '.usermenu #usermenu-carousel .carousel-navigation-link',
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*/
|
||||
const registerEventListeners = () => {
|
||||
const userMenu = document.querySelector(selectors.userMenu);
|
||||
|
||||
// Handle the 'shown.bs.dropdown' event (Fired when the dropdown menu is fully displayed).
|
||||
$(selectors.userMenu).on('shown.bs.dropdown', () => {
|
||||
const activeCarouselItem = document.querySelector(selectors.userMenuCarouselItemActive);
|
||||
// Set the focus on the active carousel item.
|
||||
activeCarouselItem.focus();
|
||||
|
||||
userMenu.querySelectorAll(selectors.userMenuCarouselItem).forEach(element => {
|
||||
// Resize all non-active carousel items to match the height and width of the current active (main)
|
||||
// carousel item to avoid sizing inconsistencies. This has to be done once the dropdown menu is fully
|
||||
// displayed ('shown.bs.dropdown') as the offsetWidth and offsetHeight cannot be obtained when the
|
||||
// element is hidden.
|
||||
if (!element.classList.contains('active')) {
|
||||
element.style.width = activeCarouselItem.offsetWidth + 'px';
|
||||
element.style.height = activeCarouselItem.offsetHeight + 'px';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle click events in the user menu.
|
||||
userMenu.addEventListener('click', (e) => {
|
||||
|
||||
// Handle click event on the carousel navigation (control) links in the user menu.
|
||||
if (e.target.matches(selectors.userMenuCarouselNavigationLink)) {
|
||||
carouselManagement(e);
|
||||
}
|
||||
});
|
||||
|
||||
userMenu.addEventListener('keydown', e => {
|
||||
// Handle keydown event on the carousel navigation (control) links in the user menu.
|
||||
if ((e.keyCode === space ||
|
||||
e.keyCode === enter) &&
|
||||
e.target.matches(selectors.userMenuCarouselNavigationLink)) {
|
||||
e.preventDefault();
|
||||
carouselManagement(e);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* We do the same actions here even if the caller was a click or button press.
|
||||
*
|
||||
* @param {Event} e The triggering element and key presses etc.
|
||||
*/
|
||||
const carouselManagement = e => {
|
||||
// By default the user menu dropdown element closes on a click event. This behaviour is not desirable
|
||||
// as we need to be able to navigate through the carousel items (submenus of the user menu) within the
|
||||
// user menu. Therefore, we need to prevent the propagation of this event and then manually call the
|
||||
// carousel transition.
|
||||
e.stopPropagation();
|
||||
// The id of the targeted carousel item.
|
||||
const targetedCarouselItemId = e.target.dataset.carouselTargetId;
|
||||
const targetedCarouselItem = userMenu.querySelector('#' + targetedCarouselItemId);
|
||||
// Get the position (index) of the targeted carousel item within the parent container element.
|
||||
const index = Array.from(targetedCarouselItem.parentNode.children).indexOf(targetedCarouselItem);
|
||||
// Navigate to the targeted carousel item.
|
||||
$(selectors.userMenuCarousel).carousel(index);
|
||||
|
||||
};
|
||||
|
||||
// Handle the 'hide.bs.dropdown' event (Fired when the dropdown menu is being closed).
|
||||
$(selectors.userMenu).on('hide.bs.dropdown', () => {
|
||||
// Reset the state once the user menu dropdown is closed and return back to the first (main) carousel item
|
||||
// if necessary.
|
||||
$(selectors.userMenuCarousel).carousel(0);
|
||||
});
|
||||
|
||||
// Handle the 'slid.bs.carousel' event (Fired when the carousel has completed its slide transition).
|
||||
$(selectors.userMenuCarousel).on('slid.bs.carousel', () => {
|
||||
const activeCarouselItem = userMenu.querySelector(selectors.userMenuCarouselItemActive);
|
||||
// Set the focus on the newly activated carousel item.
|
||||
activeCarouselItem.focus();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the user menu.
|
||||
*/
|
||||
const init = () => {
|
||||
registerEventListeners();
|
||||
};
|
||||
|
||||
export default {
|
||||
init: init,
|
||||
};
|
||||
@@ -1520,6 +1520,7 @@ class block_manager {
|
||||
$deleteurlparams = $this->page->url->params();
|
||||
$deletepage->set_url($deleteurlbase, $deleteurlparams);
|
||||
$deletepage->set_block_actions_done();
|
||||
$deletepage->set_secondarynav($this->get_secondarynav($block));
|
||||
// At this point we are either going to redirect, or display the form, so
|
||||
// overwrite global $PAGE ready for this. (Formslib refers to it.)
|
||||
$PAGE = $deletepage;
|
||||
@@ -1608,6 +1609,23 @@ class block_manager {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to check whether a block is implementing a secondary nav class and return it
|
||||
* initialised to the calling function
|
||||
*
|
||||
* @param block_base $block
|
||||
* @return \core\navigation\views\secondary
|
||||
*/
|
||||
protected function get_secondarynav(block_base $block): \core\navigation\views\secondary {
|
||||
$class = "core_block\\local\\views\\secondary";
|
||||
if (class_exists("block_{$block->name()}\\local\\views\\secondary")) {
|
||||
$class = "block_{$block->name()}\\local\\views\\secondary";
|
||||
}
|
||||
$secondarynav = new $class($this->page);
|
||||
$secondarynav->initialise();
|
||||
return $secondarynav;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle showing/processing the submission from the block editing form.
|
||||
* @return boolean true if the form was submitted and the new config saved. Does not
|
||||
@@ -1635,6 +1653,8 @@ class block_manager {
|
||||
$editpage->set_course($this->page->course);
|
||||
//$editpage->set_context($block->context);
|
||||
$editpage->set_context($this->page->context);
|
||||
$editpage->set_secondarynav($this->get_secondarynav($block));
|
||||
|
||||
if ($this->page->cm) {
|
||||
$editpage->set_cm($this->page->cm);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core\navigation\output;
|
||||
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use templatable;
|
||||
use custom_menu;
|
||||
|
||||
/**
|
||||
* more menu navigation renderable
|
||||
*
|
||||
* @package core
|
||||
* @category navigation
|
||||
* @copyright 2021 onwards Adrian Greeve
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class more_menu implements renderable, templatable {
|
||||
|
||||
protected $content;
|
||||
protected $navbarstyle;
|
||||
protected $haschildren;
|
||||
|
||||
/**
|
||||
* Constructor for this class.
|
||||
*
|
||||
* @param object $content Navigation objects.
|
||||
* @param string $navbarstyle class name.
|
||||
* @param bool $haschildren The content has children.
|
||||
*/
|
||||
public function __construct(object $content, string $navbarstyle, bool $haschildren = true) {
|
||||
$this->content = $content;
|
||||
$this->navbarstyle = $navbarstyle;
|
||||
$this->haschildren = $haschildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data for rendering a template.
|
||||
*
|
||||
* @param renderer_base $output The output
|
||||
* @return array Data for rendering a template
|
||||
*/
|
||||
public function export_for_template(renderer_base $output): array {
|
||||
$data = ['navbarstyle' => $this->navbarstyle];
|
||||
if ($this->haschildren) {
|
||||
if (!isset($this->content->children) || count($this->content->children) == 0) {
|
||||
$data = [];
|
||||
}
|
||||
$data['nodecollection'] = $this->content;
|
||||
} else {
|
||||
$data['nodearray'] = (array) $this->content;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core\navigation\output;
|
||||
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use templatable;
|
||||
use custom_menu;
|
||||
|
||||
/**
|
||||
* Primary navigation renderable
|
||||
*
|
||||
* This file combines primary nav, custom menu, lang menu and
|
||||
* usermenu into a standardized format for the frontend
|
||||
*
|
||||
* @package core
|
||||
* @category navigation
|
||||
* @copyright 2021 onwards Peter Dias
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class primary implements renderable, templatable {
|
||||
/** @var moodle_page $page the moodle page that the navigation belongs to */
|
||||
private $page = null;
|
||||
|
||||
/**
|
||||
* primary constructor.
|
||||
* @param \moodle_page $page
|
||||
*/
|
||||
public function __construct($page) {
|
||||
$this->page = $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the various menus into a standardized output.
|
||||
*
|
||||
* @param renderer_base|null $output
|
||||
* @return array
|
||||
*/
|
||||
public function export_for_template(?renderer_base $output = null): array {
|
||||
if (!$output) {
|
||||
$output = $this->page->get_renderer('core');
|
||||
}
|
||||
|
||||
$menudata = (object) array_merge($this->get_primary_nav(), $this->get_custom_menu($output));
|
||||
$moremenu = new \core\navigation\output\more_menu($menudata, 'navbar-nav', false);
|
||||
|
||||
return [
|
||||
'moremenu' => $moremenu->export_for_template($output),
|
||||
'lang' => !isloggedin() || isguestuser() ? $this->get_lang_menu($output) : [],
|
||||
'user' => $this->get_user_menu($output),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the primary nav object and standardize the output
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_primary_nav(): array {
|
||||
$nodes = [];
|
||||
foreach ($this->page->primarynav->children as $node) {
|
||||
$nodes[] = [
|
||||
'title' => $node->get_title(),
|
||||
'url' => $node->action(),
|
||||
'text' => $node->text,
|
||||
'icon' => $node->icon,
|
||||
'isactive' => $node->isactive,
|
||||
];
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom menu items reside on the same level as the original nodes.
|
||||
* Fetch and convert the nodes to a standardised array.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return array
|
||||
*/
|
||||
protected function get_custom_menu(renderer_base $output): array {
|
||||
global $CFG;
|
||||
|
||||
// Early return if a custom menu does not exists.
|
||||
if (empty($CFG->custommenuitems)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$custommenuitems = $CFG->custommenuitems;
|
||||
$currentlang = current_language();
|
||||
$custommenunodes = custom_menu::convert_text_to_menu_nodes($custommenuitems, $currentlang);
|
||||
$nodes = [];
|
||||
foreach ($custommenunodes as $node) {
|
||||
$nodes[] = $node->export_for_template($output);
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of options for the lang picker.
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return array
|
||||
*/
|
||||
protected function get_lang_menu(renderer_base $output): array {
|
||||
// Early return if a lang menu does not exists.
|
||||
if (empty($output->lang_menu())) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$currentlang = current_language();
|
||||
$langs = get_string_manager()->get_list_of_translations();
|
||||
$nodes = [];
|
||||
$activelanguage = '';
|
||||
|
||||
// Add the lang picker if needed.
|
||||
foreach ($langs as $langtype => $langname) {
|
||||
$isactive = $langtype == $currentlang;
|
||||
$node = [
|
||||
'title' => $langname,
|
||||
'text' => $langname,
|
||||
'link' => true,
|
||||
'isactive' => $isactive,
|
||||
'url' => $isactive ? new \moodle_url('#') : new \moodle_url($this->page->url, ['lang' => $langtype]),
|
||||
];
|
||||
|
||||
$nodes[] = $node;
|
||||
|
||||
if ($isactive) {
|
||||
$activelanguage = $langname;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => $activelanguage,
|
||||
'items' => $nodes,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/Generate the user menu.
|
||||
*
|
||||
* This is leveraging the data from user_get_user_navigation_info and the logic in $OUTPUT->user_menu()
|
||||
*
|
||||
* @param renderer_base $output
|
||||
* @return array
|
||||
*/
|
||||
public function get_user_menu(renderer_base $output): array {
|
||||
global $CFG, $USER, $PAGE;
|
||||
require_once($CFG->dirroot . '/user/lib.php');
|
||||
|
||||
$usermenudata = [];
|
||||
$submenusdata = [];
|
||||
$info = user_get_user_navigation_info($USER, $PAGE);
|
||||
if (isset($info->unauthenticateduser)) {
|
||||
$info->unauthenticateduser['content'] = get_string($info->unauthenticateduser['content']);
|
||||
$info->unauthenticateduser['url'] = get_login_url();
|
||||
return (array) $info;
|
||||
}
|
||||
// Gather all the avatar data to be displayed in the user menu.
|
||||
$usermenudata['avatardata'][] = [
|
||||
'content' => $info->metadata['useravatar'],
|
||||
'classes' => 'current'
|
||||
];
|
||||
$usermenudata['userfullname'] = $info->metadata['realuserfullname'] ?? $info->metadata['userfullname'];
|
||||
|
||||
// Logged in as someone else.
|
||||
if ($info->metadata['asotheruser']) {
|
||||
$usermenudata['avatardata'][] = [
|
||||
'content' => $info->metadata['realuseravatar'],
|
||||
'classes' => 'realuser'
|
||||
];
|
||||
$usermenudata['metadata'][] = [
|
||||
'content' => get_string('loggedinas', 'moodle', $info->metadata['userfullname']),
|
||||
'classes' => 'viewingas'
|
||||
];
|
||||
}
|
||||
|
||||
// Gather all the meta data to be displayed in the user menu.
|
||||
$metadata = [
|
||||
'asotherrole' => [
|
||||
'value' => 'rolename',
|
||||
'class' => 'role role-##GENERATEDCLASS##',
|
||||
],
|
||||
'userloginfail' => [
|
||||
'value' => 'userloginfail',
|
||||
'class' => 'loginfailures',
|
||||
],
|
||||
'asmnetuser' => [
|
||||
'value' => 'mnetidprovidername',
|
||||
'class' => 'mnet mnet-##GENERATEDCLASS##',
|
||||
],
|
||||
];
|
||||
foreach ($metadata as $key => $value) {
|
||||
if (!empty($info->metadata[$key])) {
|
||||
$content = $info->metadata[$value['value']] ?? '';
|
||||
$generatedclass = strtolower(preg_replace('#[ ]+#', '-', trim($content)));
|
||||
$customclass = str_replace('##GENERATEDCLASS##', $generatedclass, ($value['class'] ?? ''));
|
||||
$usermenudata['metadata'][] = [
|
||||
'content' => $content,
|
||||
'classes' => $customclass
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$modifiedarray = array_map(function($value) {
|
||||
$value->divider = $value->itemtype == 'divider';
|
||||
$value->link = $value->itemtype == 'link';
|
||||
if (isset($value->pix) && !empty($value->pix)) {
|
||||
$value->pixicon = $value->pix;
|
||||
unset($value->pix);
|
||||
}
|
||||
return $value;
|
||||
}, $info->navitems);
|
||||
|
||||
// Include the language menu as a submenu within the user menu.
|
||||
$langmenu = $this->get_lang_menu($output);
|
||||
if (!empty($langmenu)) {
|
||||
$languageitems = $langmenu['items'];
|
||||
// If there are available languages, generate the data for the the language selector submenu.
|
||||
if (!empty($languageitems)) {
|
||||
$langsubmenuid = uniqid();
|
||||
// Generate the data for the link to language selector submenu.
|
||||
$language = (object) [
|
||||
'itemtype' => 'submenu-link',
|
||||
'submenuid' => $langsubmenuid,
|
||||
'title' => get_string('language'),
|
||||
'pixicon' => 'i/language',
|
||||
'divider' => false,
|
||||
'submenulink' => true,
|
||||
];
|
||||
|
||||
// Place the link before the 'Log out' menu item which is either the last item in the menu or
|
||||
// second to last when 'Switch roles' is available.
|
||||
$menuposition = count($modifiedarray) - 1;
|
||||
if (has_capability('moodle/role:switchroles', $PAGE->context)) {
|
||||
$menuposition = count($modifiedarray) - 2;
|
||||
}
|
||||
array_splice($modifiedarray, $menuposition, 0, [$language]);
|
||||
|
||||
// Generate the data for the language selector submenu.
|
||||
$submenusdata[] = (object)[
|
||||
'id' => $langsubmenuid,
|
||||
'title' => get_string('languageselector'),
|
||||
'items' => $languageitems,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Add dividers after the first item and before the last item.
|
||||
$modifiedarray[0]->divider = true;
|
||||
$modifiedarray[count($modifiedarray) - 2]->divider = true;
|
||||
$usermenudata['items'] = $modifiedarray;
|
||||
$usermenudata['submenus'] = array_values($submenusdata);
|
||||
|
||||
return $usermenudata;
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,10 @@ class primary extends view {
|
||||
return;
|
||||
}
|
||||
$this->id = 'primary_navigation';
|
||||
$this->add(get_string('home'), new \moodle_url('/'), self::TYPE_SYSTEM,
|
||||
if (get_home_page() == HOMEPAGE_SITE && isloggedin() && !isguestuser()) {
|
||||
$this->add(get_string('home'), new \moodle_url('/'), self::TYPE_SYSTEM,
|
||||
null, 'home', new \pix_icon('i/home', ''));
|
||||
}
|
||||
|
||||
// Add the dashboard link.
|
||||
if (isloggedin() && !isguestuser()) { // Makes no sense if you aren't logged in.
|
||||
|
||||
@@ -32,6 +32,10 @@ use navigation_node;
|
||||
class secondary extends view {
|
||||
/** @var string $headertitle The header for this particular menu*/
|
||||
public $headertitle;
|
||||
|
||||
/** @var int The maximum limit of navigation nodes displayed in the secondary navigation */
|
||||
const MAX_DISPLAYED_NAV_NODES = 5;
|
||||
|
||||
/**
|
||||
* Defines the default structure for the secondary nav in a course context.
|
||||
*
|
||||
@@ -85,13 +89,13 @@ class secondary extends view {
|
||||
return [
|
||||
self::TYPE_SETTING => [
|
||||
'modedit' => 1,
|
||||
'roleoverride' => 3,
|
||||
'rolecheck' => 3.1,
|
||||
'logreport' => 4,
|
||||
"mod_{$this->page->activityname}_useroverrides" => 5, // Overrides are module specific.
|
||||
"mod_{$this->page->activityname}_groupoverrides" => 6,
|
||||
'roleassign' => 7,
|
||||
'filtermanage' => 8,
|
||||
"mod_{$this->page->activityname}_useroverrides" => 3, // Overrides are module specific.
|
||||
"mod_{$this->page->activityname}_groupoverrides" => 4,
|
||||
'roleassign' => 5,
|
||||
'filtermanage' => 6,
|
||||
'roleoverride' => 7,
|
||||
'rolecheck' => 7.1,
|
||||
'logreport' => 8,
|
||||
'backup' => 9,
|
||||
'restore' => 10,
|
||||
'competencybreakdown' => 11,
|
||||
@@ -102,6 +106,34 @@ class secondary extends view {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the keys of the course secondary nav nodes that should be forced into the "more" menu by default.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_course_more_menu_nodes(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the keys of the module secondary nav nodes that should be forced into the "more" menu by default.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_module_more_menu_nodes(): array {
|
||||
return ['roleoverride', 'rolecheck', 'logreport', 'roleassign', 'filtermanage', 'backup', 'restore',
|
||||
'competencybreakdown'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the keys of the admin secondary nav nodes that should be forced into the "more" menu by default.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_admin_more_menu_nodes(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the view based navigation based on the current context.
|
||||
*
|
||||
@@ -119,23 +151,36 @@ class secondary extends view {
|
||||
$this->id = 'secondary_navigation';
|
||||
$context = $this->context;
|
||||
$this->headertitle = get_string('menu');
|
||||
$defaultmoremenunodes = [];
|
||||
|
||||
switch ($context->contextlevel) {
|
||||
case CONTEXT_COURSE:
|
||||
if ($this->page->course->id != $SITE->id) {
|
||||
$this->headertitle = get_string('courseheader');
|
||||
$this->load_course_navigation();
|
||||
$defaultmoremenunodes = $this->get_default_course_more_menu_nodes();
|
||||
}
|
||||
break;
|
||||
case CONTEXT_MODULE:
|
||||
$this->headertitle = get_string('activityheader');
|
||||
$this->load_module_navigation();
|
||||
$defaultmoremenunodes = $this->get_default_module_more_menu_nodes();
|
||||
break;
|
||||
case CONTEXT_SYSTEM:
|
||||
$this->headertitle = get_string('homeheader');
|
||||
$this->load_admin_navigation();
|
||||
$defaultmoremenunodes = $this->get_default_admin_more_menu_nodes();
|
||||
break;
|
||||
}
|
||||
|
||||
$this->remove_unwanted_nodes();
|
||||
|
||||
// Don't need to show anything if only the view node is available. Remove it.
|
||||
if ($this->children->count() == 1) {
|
||||
$this->children->remove('modulepage');
|
||||
}
|
||||
|
||||
$this->force_nodes_into_more_menu($defaultmoremenunodes);
|
||||
// Search and set the active node.
|
||||
$this->scan_for_active_node($this);
|
||||
$this->initialised = true;
|
||||
@@ -157,14 +202,17 @@ class secondary extends view {
|
||||
$this->add(get_string('coursepage', 'admin'), $url, self::TYPE_COURSE, null, 'coursehome');
|
||||
|
||||
$nodes = $this->get_default_course_mapping();
|
||||
$nodesordered = $this->get_leaf_nodes($settingsnav, $nodes['settings']);
|
||||
$nodesordered += $this->get_leaf_nodes($navigation, $nodes['navigation']);
|
||||
$nodesordered = $this->get_leaf_nodes($settingsnav, $nodes['settings'] ?? []);
|
||||
$nodesordered += $this->get_leaf_nodes($navigation, $nodes['navigation'] ?? []);
|
||||
$this->add_ordered_nodes($nodesordered);
|
||||
|
||||
// All additional nodes will be available under the 'Course admin' page.
|
||||
$text = get_string('courseadministration');
|
||||
$url = new \moodle_url('/course/admin.php', array('courseid' => $this->page->course->id));
|
||||
$this->add($text, $url, null, null, 'courseadmin', new \pix_icon('t/edit', $text));
|
||||
$coursecontext = \context_course::instance($course->id);
|
||||
if (has_capability('moodle/course:update', $coursecontext)) {
|
||||
// All additional nodes will be available under the 'Course admin' page.
|
||||
$text = get_string('courseadministration');
|
||||
$url = new \moodle_url('/course/admin.php', array('courseid' => $this->page->course->id));
|
||||
$this->add($text, $url, null, null, 'courseadmin', new \pix_icon('t/edit', $text));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,7 +228,12 @@ class secondary extends view {
|
||||
$nodes = $this->get_default_module_mapping();
|
||||
|
||||
if ($mainnode) {
|
||||
$this->add(get_string('module', 'course'), $this->page->url, null, null, 'modulepage');
|
||||
$url = new \moodle_url('/mod/' . $this->page->activityname . '/view.php', ['id' => $this->page->cm->id]);
|
||||
$setactive = $url->compare($this->page->url, URL_MATCH_BASE);
|
||||
$node = $this->add(get_string('module', 'course'), $url, null, null, 'modulepage');
|
||||
if ($setactive) {
|
||||
$node->make_active();
|
||||
}
|
||||
// Add the initial nodes.
|
||||
$nodesordered = $this->get_leaf_nodes($mainnode, $nodes);
|
||||
$this->add_ordered_nodes($nodesordered);
|
||||
@@ -269,4 +322,41 @@ class secondary extends view {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force certain secondary navigation nodes to be displayed in the "more" menu.
|
||||
*
|
||||
* @param array $defaultmoremenunodes Array with navigation node keys of the pre-defined nodes that
|
||||
* should be added into the "more" menu by default
|
||||
*/
|
||||
protected function force_nodes_into_more_menu(array $defaultmoremenunodes = []) {
|
||||
// Counter of the navigation nodes that are initially displayed in the secondary nav
|
||||
// (excludes the nodes from the "more" menu).
|
||||
$displayednodescount = 0;
|
||||
foreach ($this->children as $child) {
|
||||
// Skip if the navigation node has been already forced into the "more" menu.
|
||||
if ($child->forceintomoremenu) {
|
||||
continue;
|
||||
}
|
||||
// If the navigation node is in the pre-defined list of nodes that should be added by default in the
|
||||
// "more" menu or the maximum limit of displayed navigation nodes has been reached.
|
||||
if (in_array($child->key, $defaultmoremenunodes) || $displayednodescount >= self::MAX_DISPLAYED_NAV_NODES) {
|
||||
// Force the node and its children into the "more" menu.
|
||||
$child->set_force_into_more_menu(true);
|
||||
continue;
|
||||
}
|
||||
$displayednodescount++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove navigation nodes that should not be displayed in the secondary navigation.
|
||||
*/
|
||||
protected function remove_unwanted_nodes() {
|
||||
foreach ($this->children as $child) {
|
||||
if (!$child->showinsecondarynavigation) {
|
||||
$child->remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,6 +345,7 @@ class icon_system_fontawesome extends icon_system_font {
|
||||
'core:i/warning' => 'fa-exclamation text-warning',
|
||||
'core:i/window_close' => 'fa-window-close',
|
||||
'core:i/withsubcat' => 'fa-plus-square',
|
||||
'core:i/language' => 'fa-language',
|
||||
'core:m/USD' => 'fa-usd',
|
||||
'core:t/addcontact' => 'fa-address-card',
|
||||
'core:t/add' => 'fa-plus',
|
||||
|
||||
@@ -61,7 +61,7 @@ class report_helper {
|
||||
|
||||
if (!empty($menu)) {
|
||||
$select = new url_select($menu, $activeurl, null, 'choosecoursereport');
|
||||
$select->set_label(get_string('report'), ['class' => 'accesshide']);
|
||||
$select->set_label(get_string('reporttype'), ['class' => 'accesshide']);
|
||||
$select->attributes['style'] = "margin-bottom: 1.5rem";
|
||||
$select->class .= " mb-4";
|
||||
echo $OUTPUT->render($select);
|
||||
|
||||
@@ -149,6 +149,10 @@ class navigation_node implements renderable {
|
||||
public $requiresajaxloading = false;
|
||||
/** @var bool If set to true this node will be added to the "flat" navigation */
|
||||
public $showinflatnavigation = false;
|
||||
/** @var bool If set to true this node will be forced into a "more" menu whenever possible */
|
||||
public $forceintomoremenu = false;
|
||||
/** @var bool If set to true this node will be displayed in the "secondary" navigation when applicable */
|
||||
public $showinsecondarynavigation = true;
|
||||
|
||||
/**
|
||||
* Constructs a new navigation_node
|
||||
@@ -840,6 +844,30 @@ class navigation_node implements renderable {
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the node and its children should be added into a "more" menu whenever possible.
|
||||
*
|
||||
* @param bool $forceintomoremenu
|
||||
*/
|
||||
public function set_force_into_more_menu(bool $forceintomoremenu = false) {
|
||||
$this->forceintomoremenu = $forceintomoremenu;
|
||||
foreach ($this->children as $child) {
|
||||
$child->set_force_into_more_menu($forceintomoremenu);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the node and its children should be displayed in the "secondary" navigation when applicable.
|
||||
*
|
||||
* @param bool $show
|
||||
*/
|
||||
public function set_show_in_secondary_navigation(bool $show = true) {
|
||||
$this->showinsecondarynavigation = $show;
|
||||
foreach ($this->children as $child) {
|
||||
$child->set_show_in_secondary_navigation($show);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the menu item to handle locking and unlocking of a conext.
|
||||
*
|
||||
|
||||
@@ -3981,6 +3981,10 @@ class context_header implements renderable {
|
||||
* page => page object. Don't include if the image is an external image.
|
||||
*/
|
||||
public $additionalbuttons;
|
||||
/**
|
||||
* @var string $prefix A string that is before the title.
|
||||
*/
|
||||
public $prefix;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -3989,8 +3993,9 @@ class context_header implements renderable {
|
||||
* @param int $headinglevel Main heading 'h' tag level.
|
||||
* @param string|null $imagedata HTML code for the picture in the page header.
|
||||
* @param string $additionalbuttons Buttons for the header e.g. Messaging button for the user header.
|
||||
* @param string $prefix Text that precedes the heading.
|
||||
*/
|
||||
public function __construct($heading = null, $headinglevel = 1, $imagedata = null, $additionalbuttons = null) {
|
||||
public function __construct($heading = null, $headinglevel = 1, $imagedata = null, $additionalbuttons = null, $prefix = null) {
|
||||
|
||||
$this->heading = $heading;
|
||||
$this->headinglevel = $headinglevel;
|
||||
@@ -4000,6 +4005,7 @@ class context_header implements renderable {
|
||||
if (isset($this->additionalbuttons)) {
|
||||
$this->format_button_images();
|
||||
}
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+21
-32
@@ -3321,41 +3321,26 @@ EOD;
|
||||
|
||||
$loginpage = $this->is_login_page();
|
||||
$loginurl = get_login_url();
|
||||
// If not logged in, show the typical not-logged-in string.
|
||||
if (!isloggedin()) {
|
||||
$returnstr = get_string('loggedinnot', 'moodle');
|
||||
if (!$loginpage) {
|
||||
$returnstr .= " (<a href=\"$loginurl\">" . get_string('login') . '</a>)';
|
||||
}
|
||||
return html_writer::div(
|
||||
html_writer::span(
|
||||
$returnstr,
|
||||
'login nav-link'
|
||||
),
|
||||
$usermenuclasses
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// If logged in as a guest user, show a string to that effect.
|
||||
if (isguestuser()) {
|
||||
$returnstr = get_string('loggedinasguest');
|
||||
if (!$loginpage && $withlinks) {
|
||||
$returnstr .= " (<a href=\"$loginurl\">".get_string('login').'</a>)';
|
||||
}
|
||||
|
||||
return html_writer::div(
|
||||
html_writer::span(
|
||||
$returnstr,
|
||||
'login nav-link'
|
||||
),
|
||||
$usermenuclasses
|
||||
);
|
||||
}
|
||||
|
||||
// Get some navigation opts.
|
||||
$opts = user_get_user_navigation_info($user, $this->page);
|
||||
|
||||
if (!empty($opts->unauthenticateduser)) {
|
||||
$returnstr = get_string($opts->unauthenticateduser['content'], 'moodle');
|
||||
// If not logged in, show the typical not-logged-in string.
|
||||
if (!$loginpage && (!$opts->unauthenticateduser['guest'] || $withlinks)) {
|
||||
$returnstr .= " (<a href=\"$loginurl\">" . get_string('login') . '</a>)';
|
||||
}
|
||||
|
||||
return html_writer::div(
|
||||
html_writer::span(
|
||||
$returnstr,
|
||||
'login nav-link'
|
||||
),
|
||||
$usermenuclasses
|
||||
);
|
||||
}
|
||||
|
||||
$avatarclasses = "avatars";
|
||||
$avatarcontents = html_writer::span($opts->metadata['useravatar'], 'avatar current');
|
||||
$usertextcontents = $opts->metadata['userfullname'];
|
||||
@@ -4293,10 +4278,14 @@ EOD;
|
||||
// Image data.
|
||||
if (isset($contextheader->imagedata)) {
|
||||
// Header specific image.
|
||||
$html .= html_writer::div($contextheader->imagedata, 'page-header-image');
|
||||
$html .= html_writer::div($contextheader->imagedata, 'page-header-image icon-size-7');
|
||||
}
|
||||
|
||||
// Headings.
|
||||
if (isset($contextheader->prefix)) {
|
||||
$prefix = html_writer::div($contextheader->prefix, 'text-muted');
|
||||
$heading = $prefix . $heading;
|
||||
}
|
||||
$html .= html_writer::tag('div', $heading, array('class' => 'page-header-headings'));
|
||||
|
||||
// Buttons.
|
||||
|
||||
+57
-1
@@ -28,6 +28,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
use core\navigation\views\primary;
|
||||
use core\navigation\views\secondary;
|
||||
use core\navigation\output\primary as primaryoutput;
|
||||
|
||||
/**
|
||||
* $PAGE is a central store of information about the current page we are
|
||||
@@ -84,6 +85,7 @@ use core\navigation\views\secondary;
|
||||
* @property-read secondary $secondarynav The secondary navigation object
|
||||
* used to display the secondarynav in boost
|
||||
* @property-read primary $primarynav The primary navigation object used to display the primary nav in boost
|
||||
* @property-read primaryoutput $primarynavcombined The primary navigation object used to display the primary nav in boost
|
||||
* @property-read global_navigation $navigation The navigation structure for this page.
|
||||
* @property-read xhtml_container_stack $opencontainers Tracks XHTML tags on this page that have been opened but not closed.
|
||||
* mainly for internal use by the rendering code.
|
||||
@@ -311,6 +313,12 @@ class moodle_page {
|
||||
*/
|
||||
protected $_primarynav = null;
|
||||
|
||||
/**
|
||||
* @var primaryoutput Contains the combined nav nodes that will appear
|
||||
* in the primary navigation. Includes - primarynav, langmenu, usermenu
|
||||
*/
|
||||
protected $_primarynavcombined = null;
|
||||
|
||||
/**
|
||||
* @var navbar Contains the navbar structure.
|
||||
*/
|
||||
@@ -380,6 +388,11 @@ class moodle_page {
|
||||
*/
|
||||
protected $_regionmainsettingsinheader = false;
|
||||
|
||||
/**
|
||||
* @var bool Should the secondary menu be rendered.
|
||||
*/
|
||||
protected $_hassecondarynavigation = true;
|
||||
|
||||
/**
|
||||
* Force the settings menu to be displayed on this page. This will only force the
|
||||
* settings menu on an activity / resource page that is being displayed on a theme that
|
||||
@@ -806,7 +819,13 @@ class moodle_page {
|
||||
*/
|
||||
protected function magic_get_secondarynav() {
|
||||
if ($this->_secondarynav === null) {
|
||||
$this->_secondarynav = new secondary($this);
|
||||
$class = 'core\navigation\views\secondary';
|
||||
// Try and load a custom class first.
|
||||
if (class_exists("mod_{$this->activityname}\\local\\views\\secondary")) {
|
||||
$class = "mod_{$this->activityname}\\local\\views\\secondary";
|
||||
}
|
||||
|
||||
$this->_secondarynav = new $class($this);
|
||||
$this->_secondarynav->initialise();
|
||||
}
|
||||
return $this->_secondarynav;
|
||||
@@ -824,6 +843,17 @@ class moodle_page {
|
||||
return $this->_primarynav;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary navigation object
|
||||
* @return primary
|
||||
*/
|
||||
protected function magic_get_primarynavcombined() {
|
||||
if ($this->_primarynavcombined === null) {
|
||||
$this->_primarynavcombined = new primaryoutput($this);
|
||||
}
|
||||
return $this->_primarynavcombined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns request IP address.
|
||||
*
|
||||
@@ -1240,6 +1270,14 @@ class moodle_page {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force set secondary_nav. Useful in cases where we dealing with non course modules. e.g. blocks, tools.
|
||||
* @param secondary $nav
|
||||
*/
|
||||
public function set_secondarynav(secondary $nav) {
|
||||
$this->_secondarynav = $nav;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a CSS class to the body tag of the page.
|
||||
*
|
||||
@@ -2133,4 +2171,22 @@ class moodle_page {
|
||||
public function include_region_main_settings_in_header_actions() : bool {
|
||||
return $this->_regionmainsettingsinheader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flag to indicate if the secondary navigation should be rendered.
|
||||
*
|
||||
* @param bool $value If the secondary navigation should be rendered.
|
||||
*/
|
||||
public function has_secondary_navigation_setter(bool $value) : void {
|
||||
$this->_hassecondarynavigation = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the secondary navigation should be rendered.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_secondary_navigation() : bool {
|
||||
return $this->_hassecondarynavigation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,19 @@
|
||||
<div class="col-12 pt-3 pb-3">
|
||||
<div class="card {{^contextheader}}border-0 bg-transparent{{/contextheader}}">
|
||||
<div class="card-body {{^contextheader}}p-2{{/contextheader}}">
|
||||
<div class="d-flex flex-wrap">
|
||||
{{#hasnavbar}}
|
||||
<div id="page-navbar">
|
||||
{{{navbar}}}
|
||||
</div>
|
||||
{{/hasnavbar}}
|
||||
<div class="ml-auto d-flex">
|
||||
{{{pageheadingbutton}}}
|
||||
</div>
|
||||
<div id="course-header">
|
||||
{{{courseheader}}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-sm-flex align-items-center">
|
||||
{{#contextheader}}
|
||||
<div class="mr-auto">
|
||||
@@ -50,19 +63,6 @@
|
||||
{{/headeractions}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap">
|
||||
{{#hasnavbar}}
|
||||
<div id="page-navbar">
|
||||
{{{navbar}}}
|
||||
</div>
|
||||
{{/hasnavbar}}
|
||||
<div class="ml-auto d-flex">
|
||||
{{{pageheadingbutton}}}
|
||||
</div>
|
||||
<div id="course-header">
|
||||
{{{courseheader}}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/moremenu
|
||||
|
||||
The More menu.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"nodecollection": {
|
||||
"children": [
|
||||
{
|
||||
"text": "Home",
|
||||
"action": "/index.php?redirect=0",
|
||||
"active": "true"
|
||||
},
|
||||
{
|
||||
"text": "Dashboard",
|
||||
"action": "/my"
|
||||
},
|
||||
{
|
||||
"text": "Courses",
|
||||
"action": "/course"
|
||||
},
|
||||
{
|
||||
"text": "Site Administration",
|
||||
"action": "/admin/search.php"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}}
|
||||
<nav class="moremenu">
|
||||
<ul id="moremenu-{{ uniqid }}-{{navbarstyle}}" role="menubar" class="nav more-nav {{navbarstyle}}">
|
||||
{{#nodecollection}}
|
||||
{{#children}}
|
||||
{{> core/moremenu_children}}
|
||||
{{/children}}
|
||||
{{/nodecollection}}
|
||||
{{#nodearray}}
|
||||
{{> core/moremenu_children}}
|
||||
{{/nodearray}}
|
||||
<li role="none" class="nav-item dropdown dropdownmoremenu d-none" data-region="morebutton">
|
||||
<a class="dropdown-toggle nav-link" href="#" id="moremenu-dropdown{{ uniqid }}" role="menuitem" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" tabindex="-1">
|
||||
{{#str}}moremenu, core{{/str}}
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-right" data-region="moredropdown" aria-labelledby="moremenu-dropdown{{ uniqid }}" role="menu">
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{{#js}}
|
||||
require(['core/moremenu'], function(moremenu) {
|
||||
moremenu(document.querySelector('#moremenu-{{ uniqid }}-{{navbarstyle}}'));
|
||||
});
|
||||
{{/js}}
|
||||
@@ -0,0 +1,63 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/moremenu_children
|
||||
|
||||
The More menu children
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"divider": "",
|
||||
"haschildren": "",
|
||||
"uniqid": "Unique string",
|
||||
"text": "Moodle community",
|
||||
"children": "",
|
||||
"title": "Moodle community",
|
||||
"url": "https://moodle.org"
|
||||
}
|
||||
}}
|
||||
{{#haschildren}}
|
||||
<li class="dropdown nav-item" role="none" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
|
||||
<a class="dropdown-toggle nav-link" id="drop-down-{{uniqid}}" role="menuitem" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" href="#" aria-controls="drop-down-menu-{{uniqid}}" tabindex="-1">
|
||||
{{{text}}}
|
||||
</a>
|
||||
<div class="dropdown-menu" role="menu" id="drop-down-menu-{{uniqid}}" aria-labelledby="drop-down-{{uniqid}}">
|
||||
{{#children}}
|
||||
{{^divider}}
|
||||
<a class="dropdown-item" role="menuitem" href="{{{url}}}">{{{text}}}</a>
|
||||
{{/divider}}
|
||||
{{#divider}}
|
||||
<div class="dropdown-divider"></div>
|
||||
{{/divider}}
|
||||
{{/children}}
|
||||
</div>
|
||||
</li>
|
||||
{{/haschildren}}
|
||||
{{^haschildren}}
|
||||
<li class="nav-item" role="none" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
|
||||
{{#tab}}
|
||||
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{tab}}" data-toggle="tab" aria-current="false" tabindex="-1">
|
||||
{{{text}}}
|
||||
</a>
|
||||
{{/tab}}
|
||||
{{^tab}}
|
||||
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{{url}}}{{{action}}}" {{#isactive}}aria-current="true"{{/isactive}} tabindex="-1">
|
||||
{{{text}}}
|
||||
</a>
|
||||
{{/tab}}
|
||||
</li>
|
||||
{{/haschildren}}
|
||||
@@ -36,28 +36,32 @@
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<!-- First the top most node and immediate children -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#link{{node.key}}" data-toggle="tab" role="tab" aria-selected="true">{{node.text}}</a>
|
||||
</li>
|
||||
<!-- Now the first level children with sub nodes -->
|
||||
{{#node.children}}
|
||||
{{#children.count}}
|
||||
{{#display}}
|
||||
{{^is_short_branch}}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#link{{key}}" data-toggle="tab" role="tab" aria-selected="false" tabindex="-1">{{text}}</a>
|
||||
</li>
|
||||
{{/is_short_branch}}
|
||||
{{/display}}
|
||||
{{/children.count}}
|
||||
{{/node.children}}
|
||||
</ul>
|
||||
{{#secondarynavigation}}
|
||||
{{> core/moremenu}}
|
||||
{{/secondarynavigation}}
|
||||
{{^secondarynavigation}}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<!-- First the top most node and immediate children -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#link{{node.key}}" data-toggle="tab" role="tab" aria-selected="true">{{node.text}}</a>
|
||||
</li>
|
||||
<!-- Now the first level children with sub nodes -->
|
||||
{{#node.children}}
|
||||
{{#children.count}}
|
||||
{{#display}}
|
||||
{{^is_short_branch}}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#link{{key}}" data-toggle="tab" role="tab" aria-selected="false" tabindex="-1">{{text}}</a>
|
||||
</li>
|
||||
{{/is_short_branch}}
|
||||
{{/display}}
|
||||
{{/children.count}}
|
||||
{{/node.children}}
|
||||
</ul>
|
||||
{{/secondarynavigation}}
|
||||
|
||||
<div class="tab-content mt-3">
|
||||
<div class="tab-pane active" id="link{{node.key}}" role="tabpanel">
|
||||
<div class="tab-pane active" id="link{{node.key}}" {{^secondarynavigation}}role="tabpanel"{{/secondarynavigation}}>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
@@ -101,7 +105,7 @@
|
||||
</div>
|
||||
{{#node.children}}
|
||||
{{#children.count}}
|
||||
<div class="tab-pane" id="link{{key}}" role="tabpanel">
|
||||
<div class="tab-pane" id="link{{key}}" {{^secondarynavigation}}role="tabpanel"{{/secondarynavigation}}>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/user_action_menu_items
|
||||
|
||||
Template for user action menu items.
|
||||
|
||||
Context variables required for this template:
|
||||
* items - The different items to be rendered
|
||||
* link - If a link is provided render it.
|
||||
* title - The text to be shown for the link.
|
||||
* url - The href for the link.
|
||||
* pixicon - (Optional) The Moodle icon to use
|
||||
* imgsrc - (Optional) If provided, uses this as source for an image tag. Note: pixicon is preferred.
|
||||
* submenulink - If a submenu link is provided render it.
|
||||
* submenuid - The id of the targeted submenu.
|
||||
* title - The text to be shown for the link.
|
||||
* pixicon - (Optional) The Moodle icon to use.
|
||||
* imgsrc - (Optional) If provided, uses this as source for an image tag. Note: pixicon is preferred.
|
||||
* divider - Whether a divider is to be displayed or not
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"link": {
|
||||
"title": "Github user",
|
||||
"url": "https://raw.githubusercontent.com/",
|
||||
"pixicon": "t/dashboard",
|
||||
"imgsrc": "https://raw.githubusercontent.com/moodle/moodle/master/pix/t/check.png"
|
||||
},
|
||||
"divider": 1
|
||||
},
|
||||
{
|
||||
"submenulink": {
|
||||
"title": "Title",
|
||||
"submenuid": "86cebd87",
|
||||
"pixicon": "t/dashboard",
|
||||
"imgsrc": "https://raw.githubusercontent.com/moodle/moodle/master/pix/t/check.png"
|
||||
},
|
||||
"divider": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
{{#items}}
|
||||
{{#link}}
|
||||
<a href="{{{url}}}" class="dropdown-item" role="menuitem">
|
||||
{{#pixicon}}
|
||||
{{#pix}}{{pixicon}}{{/pix}}
|
||||
{{/pixicon}}
|
||||
{{^pixicon}}
|
||||
{{#imgsrc}}<img aria-hidden="true" src="{{imgsrc}}" alt="{{title}}"/>{{/imgsrc}}
|
||||
{{/pixicon}}
|
||||
{{title}}
|
||||
</a>
|
||||
{{/link}}
|
||||
{{#submenulink}}
|
||||
<a href="#" class="carousel-navigation-link dropdown-item" role="menuitem" data-carousel-target-id="carousel-item-{{submenuid}}">
|
||||
{{#pixicon}}
|
||||
{{#pix}}{{pixicon}}{{/pix}}
|
||||
{{/pixicon}}
|
||||
{{^pixicon}}
|
||||
{{#imgsrc}}<img aria-hidden="true" src="{{imgsrc}}" alt="{{title}}"/>{{/imgsrc}}
|
||||
{{/pixicon}}
|
||||
{{title}}
|
||||
</a>
|
||||
{{/submenulink}}
|
||||
{{#divider}}<div class="dropdown-divider"></div>{{/divider}}
|
||||
{{/items}}
|
||||
@@ -0,0 +1,48 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/user_action_menu_submenus
|
||||
|
||||
Template for the submenus in the user action menu.
|
||||
|
||||
Context variables required for this template:
|
||||
* items - The submenu items
|
||||
* link - If a link is provided render it.
|
||||
* title - The title added to the link.
|
||||
* text - The text to be shown for the link.
|
||||
* url - The href for the link.
|
||||
* isactive - (Optional) Whether the item is currently active (has been selected).
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"items": {
|
||||
"link": {
|
||||
"title": "Submenu item 1",
|
||||
"text": "Submenu item 1",
|
||||
"url": "https://example.com/",
|
||||
"isactive": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
{{#items}}
|
||||
{{#link}}
|
||||
<a href="{{{url}}}" class="dropdown-item pl-5" role="menuitem" {{#isactive}}aria-current="true"{{/isactive}}>
|
||||
{{text}}
|
||||
</a>
|
||||
{{/link}}
|
||||
{{/items}}
|
||||
@@ -0,0 +1,105 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/user_menu
|
||||
|
||||
Action link template.
|
||||
|
||||
Context variables required for this template:
|
||||
* unauthenticateduseruser - (Optional) Items to be displayed if an an unautheticated user is accessing the site
|
||||
* content - The content to be displayed in the header.
|
||||
* url - The login url
|
||||
* items - Array of user menu items used in user_action_menu_items. Required if the above not provided.
|
||||
* metadata - Array of additional metadata to be displayed in the dropdown button.
|
||||
* avatardata - Array of avatars to be displayed. Usually only the current user's avatar. If viewing as another user,
|
||||
includes that user's avatar.
|
||||
* userfullname - The name of the logged in user
|
||||
* submenus - Array of submenus within the user menu.
|
||||
* id - The id of the submenu.
|
||||
* title - The title of the submenu.
|
||||
* items - Array of the submenu items used in core/user_action_menu_submenu_items.
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"unauthenticateduser": {
|
||||
"content": "You are not logged in",
|
||||
"url": "https://yourmoodlesite/login/index.php"
|
||||
},
|
||||
"items": [],
|
||||
"metadata": [],
|
||||
"avatardata": [],
|
||||
"userfullname": "Admin User",
|
||||
"submenus": [
|
||||
{
|
||||
"id": "86cebd87",
|
||||
"title": "Submenu title",
|
||||
"items": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
<div class="usermenu">
|
||||
{{#unauthenticateduser}}
|
||||
<span class="login pl-2">
|
||||
{{content}}
|
||||
{{#url}}
|
||||
(<a href="{{url}}">{{#str}} login, core {{/str}}</a>)
|
||||
{{/url}}
|
||||
</span>
|
||||
{{/unauthenticateduser}}
|
||||
{{^unauthenticateduser}}
|
||||
<div class="dropdown show">
|
||||
<button type="button" id="user-menu-toggle" data-toggle="dropdown" aria-label="{{#str}}usermenu{{/str}}"
|
||||
aria-haspopup="true" aria-controls="user-action-menu" class="btn dropdown-toggle">
|
||||
<span class="userbutton">
|
||||
{{> core/user_menu_metadata }}
|
||||
</span>
|
||||
</button>
|
||||
<div id="user-action-menu" class="dropdown-menu dropdown-menu-right">
|
||||
<div id="usermenu-carousel" class="carousel slide" data-touch="false" data-interval="false" data-keyboard="false">
|
||||
<div class="carousel-inner">
|
||||
<div id="carousel-item-main" class="carousel-item active" role="menu" tabindex="-1" aria-label="{{#str}}user{{/str}}">
|
||||
{{> core/user_action_menu_items }}
|
||||
</div>
|
||||
{{#submenus}}
|
||||
<div id="carousel-item-{{id}}" class="carousel-item submenu" tabindex="-1" aria-label="{{title}}">
|
||||
<div class="d-flex flex-column h-100">
|
||||
<div class="header">
|
||||
<button type="#" class="btn btn-icon carousel-navigation-link text-decoration-none text-body" data-carousel-target-id="carousel-item-main" aria-label="{{#str}}usermenugoback{{/str}}">
|
||||
<span class="dir-rtl-hide">{{#pix}}i/arrow-left{{/pix}}</span>
|
||||
<span class="dir-ltr-hide">{{#pix}}i/arrow-right{{/pix}}</span>
|
||||
</button>
|
||||
<span class="pl-2" id="carousel-item-title-{{id}}">{{title}}</span>
|
||||
</div>
|
||||
<div class="dropdown-divider"></div>
|
||||
<div class="items h-100 overflow-auto" role="menu" aria-labelledby="carousel-item-title-{{id}}">
|
||||
{{> core/user_action_menu_submenu_items }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/submenus}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/unauthenticateduser}}
|
||||
</div>
|
||||
{{#js}}
|
||||
require(['core/usermenu'], function(UserMenu) {
|
||||
UserMenu.init();
|
||||
});
|
||||
{{/js}}
|
||||
@@ -0,0 +1,63 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/user_menu_metadata
|
||||
|
||||
User menu metadata template.
|
||||
|
||||
Context variables required for this template:
|
||||
* metadata - Array of additional metadata to be displayed in the dropdown button.
|
||||
* classes - Additional classes to be applied to the content
|
||||
* content - The content to be displayed. May/may not have html within it.
|
||||
* avatardata - Array of avatars to be displayed. Usually only the current user's avatar. If viewing as another user,
|
||||
includes that user's avatar.
|
||||
* classes - Additional classes to be applied to the content
|
||||
* content - The content to be displayed. May/may not have html within it.
|
||||
* userfullname - The name of the logged in user
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"metadata": [
|
||||
{
|
||||
"classes": "loginfailures",
|
||||
"content": "<span>1</span> failed login attempts"
|
||||
}
|
||||
],
|
||||
"avatardata": [
|
||||
{
|
||||
"classes": "current",
|
||||
"content": "<img src=\"http://localhost/stable_master/theme/image.php/boost/core/1617863836/u/f2\" class=\"userpicture defaultuserpic\">"
|
||||
}
|
||||
],
|
||||
"userfullname": "Admin User"
|
||||
}
|
||||
}}
|
||||
<span class="usertext mr-1">
|
||||
{{userfullname}}
|
||||
{{#metadata}}
|
||||
<span class="meta {{classes}}">
|
||||
{{{content}}}
|
||||
</span>
|
||||
{{/metadata}}
|
||||
</span>
|
||||
<span class="avatars">
|
||||
{{#avatardata}}
|
||||
<span class="avatar {{classes}}">
|
||||
{{{content}}}
|
||||
</span>
|
||||
{{/avatardata}}
|
||||
</span>
|
||||
@@ -41,6 +41,20 @@ use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
|
||||
*/
|
||||
class behat_navigation extends behat_base {
|
||||
|
||||
/**
|
||||
* Checks whether a navigation node is active within the block navigation.
|
||||
*
|
||||
* @Given i should see :name is active in navigation
|
||||
*
|
||||
* @throws ElementNotFoundException
|
||||
* @param string $element The name of the nav elemnent to look for.
|
||||
* @return void
|
||||
*/
|
||||
public function i_should_see_is_active_in_navigation($element) {
|
||||
$this->execute("behat_general::assert_element_contains_text",
|
||||
[$element, '.block_navigation .active_tree_node', 'css_element']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get a navigation nodes text element given its text from within the navigation block.
|
||||
*
|
||||
@@ -151,7 +165,7 @@ class behat_navigation extends behat_base {
|
||||
|
||||
if ($this->running_javascript()) {
|
||||
// The user menu must be expanded when JS is enabled.
|
||||
$xpath = "//div[contains(concat(' ', @class, ' '), ' usermenu ')]//a[contains(concat(' ', @class, ' '), ' dropdown-toggle ')]";
|
||||
$xpath = "//div[contains(concat(' ', @class, ' '), ' usermenu ')]//button[contains(concat(' ', @class, ' '), ' dropdown-toggle ')]";
|
||||
$this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element"));
|
||||
}
|
||||
|
||||
@@ -925,7 +939,14 @@ class behat_navigation extends behat_base {
|
||||
if ($parentnodes) {
|
||||
$tabname = behat_context_helper::escape($parentnodes[0]);
|
||||
$tabxpath = '//ul[@role=\'tablist\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
|
||||
if ($node = $this->getSession()->getPage()->find('xpath', $tabxpath)) {
|
||||
$menubarxpath = '//ul[@role=\'menubar\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
|
||||
$linkname = behat_context_helper::escape(get_string('moremenu'));
|
||||
$menubarmorexpath = '//ul[@role=\'menubar\']/li/a[contains(normalize-space(.), ' . $linkname . ')]';
|
||||
$tabnode = $this->getSession()->getPage()->find('xpath', $tabxpath);
|
||||
$menunode = $this->getSession()->getPage()->find('xpath', $menubarxpath);
|
||||
$menubuttons = $this->getSession()->getPage()->findAll('xpath', $menubarmorexpath);
|
||||
if ($tabnode || $menunode) {
|
||||
$node = is_object($tabnode) ? $tabnode : $menunode;
|
||||
if ($this->running_javascript()) {
|
||||
$this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
|
||||
// Click on the tab and add 'active' tab to the xpath.
|
||||
@@ -936,6 +957,22 @@ class behat_navigation extends behat_base {
|
||||
$xpath .= '//div[@id = ' . $tabid . ']';
|
||||
}
|
||||
array_shift($parentnodes);
|
||||
} else if (count($menubuttons) > 0) {
|
||||
try {
|
||||
$menubuttons[0]->isVisible();
|
||||
try {
|
||||
$this->execute('behat_general::i_click_on', [$menubuttons[1], 'NodeElement']);
|
||||
} catch (Exception $e) {
|
||||
$this->execute('behat_general::i_click_on', [$menubuttons[0], 'NodeElement']);
|
||||
}
|
||||
$moreitemxpath = '//ul[@data-region=\'moredropdown\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
|
||||
if ($morenode = $this->getSession()->getPage()->find('xpath', $moreitemxpath)) {
|
||||
$this->execute('behat_general::i_click_on', [$morenode, 'NodeElement']);
|
||||
$xpath .= '//div[contains(@class,\'active\')]';
|
||||
array_shift($parentnodes);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1065,4 +1102,95 @@ class behat_navigation extends behat_base {
|
||||
}
|
||||
$this->execute('behat_general::i_visit', [$url]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* First checks to see if we are on this page via the breadcrumb. If not we then attempt to follow the link name given.
|
||||
*
|
||||
* @param string $pagename Name of the breadcrumb item to check and follow.
|
||||
*/
|
||||
public function go_to_breadcrumb_location(string $pagename): void {
|
||||
$link = $this->getSession()->getPage()->find(
|
||||
'xpath',
|
||||
"//nav[@aria-label='Navigation bar']/ol/li[last()][contains(normalize-space(.), '" . $pagename . "')]"
|
||||
);
|
||||
if (!$link) {
|
||||
$this->execute("behat_general::click_link", $pagename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an item exists in the user menu.
|
||||
*
|
||||
* @Given :itemtext :selectortype should exist in the user menu
|
||||
* @Given :itemtext :selectortype should :not exist in the user menu
|
||||
*
|
||||
* @throws ElementNotFoundException
|
||||
* @param string $itemtext The menu item to find
|
||||
* @param string $selectortype The selector type
|
||||
* @param string|null $not Instructs to checks whether the element does not exist in the user menu, if defined
|
||||
* @return void
|
||||
*/
|
||||
public function should_exist_in_user_menu($itemtext, $selectortype, $not = null) {
|
||||
$callfunction = is_null($not) ? 'should_exist_in_the' : 'should_not_exist_in_the';
|
||||
$this->execute("behat_general::{$callfunction}",
|
||||
[$itemtext, $selectortype, $this->get_user_menu_xpath(), 'xpath_element']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether an item exists in a given user submenu.
|
||||
*
|
||||
* @Given :itemtext :selectortype should exist in the :submenuname user submenu
|
||||
* @Given :itemtext :selectortype should :not exist in the :submenuname user submenu
|
||||
*
|
||||
* @throws ElementNotFoundException
|
||||
* @param string $itemtext The submenu item to find
|
||||
* @param string $selectortype The selector type
|
||||
* @param string $submenuname The name of the submenu
|
||||
* @param string|null $not Instructs to checks whether the element does not exist in the user menu, if defined
|
||||
* @return void
|
||||
*/
|
||||
public function should_exist_in_user_submenu($itemtext, $selectortype, $submenuname, $not = null) {
|
||||
$callfunction = is_null($not) ? 'should_exist_in_the' : 'should_not_exist_in_the';
|
||||
$this->execute("behat_general::{$callfunction}",
|
||||
[$itemtext, $selectortype, $this->get_user_submenu_xpath($submenuname), 'xpath_element']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given user submenu is visible.
|
||||
*
|
||||
* @Then /^I should see "(?P<submenu_string>[^"]*)" user submenu$/
|
||||
*
|
||||
* @throws ElementNotFoundException
|
||||
* @throws ExpectationException
|
||||
* @param string $submenuname The name of the submenu
|
||||
* @return void
|
||||
*/
|
||||
public function i_should_see_user_submenu($submenuname) {
|
||||
$this->execute('behat_general::should_be_visible',
|
||||
array($this->get_user_submenu_xpath($submenuname), 'xpath_element'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the xpath for the user menu element.
|
||||
*
|
||||
* @return string The xpath
|
||||
*/
|
||||
protected function get_user_menu_xpath() {
|
||||
return "//div[contains(concat(' ', @class, ' '), ' usermenu ')]" .
|
||||
"//div[contains(concat(' ', @class, ' '), ' dropdown-menu ')]" .
|
||||
"//div[@id='carousel-item-main']";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the xpath for a given user submenu element.
|
||||
*
|
||||
* @param string $submenuname The name of the submenu
|
||||
* @return string The xpath
|
||||
*/
|
||||
protected function get_user_submenu_xpath($submenuname) {
|
||||
return "//div[contains(concat(' ', @class, ' '), ' usermenu ')]" .
|
||||
"//div[contains(concat(' ', @class, ' '), ' dropdown-menu ')]" .
|
||||
"//div[contains(concat(' ', @class, ' '), ' submenu ')][@aria-label='" . $submenuname . "']";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core\navigation\output;
|
||||
|
||||
use ReflectionMethod;
|
||||
|
||||
/**
|
||||
* Primary navigation renderable test
|
||||
*
|
||||
* @package core
|
||||
* @category navigation
|
||||
* @copyright 2021 onwards Peter Dias
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class primary_test extends \advanced_testcase {
|
||||
/**
|
||||
* Basic setup to make sure the nav objects gets generated without any issues.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
global $PAGE;
|
||||
$this->resetAfterTest();
|
||||
$pagecourse = $this->getDataGenerator()->create_course();
|
||||
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $pagecourse->id]);
|
||||
$cm = get_coursemodule_from_id('assign', $assign->cmid);
|
||||
$contextrecord = \context_module::instance($cm->id);
|
||||
$pageurl = new \moodle_url('/mod/assign/view.php', ['id' => $cm->instance]);
|
||||
$PAGE->set_cm($cm);
|
||||
$PAGE->set_url($pageurl);
|
||||
$PAGE->set_course($pagecourse);
|
||||
$PAGE->set_context($contextrecord);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the primary export to confirm we are getting the nodes
|
||||
*
|
||||
* @dataProvider test_primary_export_provider
|
||||
* @param bool $withcustom Setup with custom menu
|
||||
* @param bool $withlang Setup with langs
|
||||
* @param string $userloggedin The type of user ('admin' or 'guest') if creating setup with logged in user,
|
||||
* otherwise consider the user as non-logged in
|
||||
* @param array $expecteditems An array of nodes expected with content in them.
|
||||
*/
|
||||
public function test_primary_export(bool $withcustom, bool $withlang, string $userloggedin, array $expecteditems) {
|
||||
global $PAGE, $CFG;
|
||||
if ($withcustom) {
|
||||
$CFG->custommenuitems = "Course search|/course/search.php
|
||||
Google|https://google.com.au/
|
||||
Netflix|https://netflix.com/au";
|
||||
}
|
||||
if ($userloggedin === 'admin') {
|
||||
$this->setAdminUser();
|
||||
} else if ($userloggedin === 'guest') {
|
||||
$this->setGuestUser();
|
||||
} else {
|
||||
$this->setUser(0);
|
||||
}
|
||||
|
||||
// Mimic multiple langs installed. To trigger responses 'get_list_of_translations'.
|
||||
// Note: The text/title of the nodes generated will be 'English(fr), English(de)' but we don't care about this.
|
||||
// We are testing whether the nodes gets generated when the lang menu is available.
|
||||
if ($withlang) {
|
||||
mkdir("$CFG->dataroot/lang/de", 0777, true);
|
||||
mkdir("$CFG->dataroot/lang/fr", 0777, true);
|
||||
// Ensure the new langs are picked up and not taken from the cache.
|
||||
$stringmanager = get_string_manager();
|
||||
$stringmanager->reset_caches(true);
|
||||
}
|
||||
|
||||
$primary = new primary($PAGE);
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$data = array_filter($primary->export_for_template($renderer));
|
||||
|
||||
// Assert that the number of returned menu items equals the expected result.
|
||||
$this->assertCount(count($expecteditems), $data);
|
||||
// Assert that returned menu items match the expected items.
|
||||
foreach ($data as $menutype => $value) {
|
||||
$this->assertTrue(in_array($menutype, $expecteditems));
|
||||
}
|
||||
// When the user is logged in (excluding guest access), assert that lang menu is included as a part of the
|
||||
// user menu when multiple languages are installed.
|
||||
if (isloggedin() && !isguestuser()) {
|
||||
// Look for a language menu item within the user menu items.
|
||||
$usermenulang = array_filter($data['user']['items'], function($usermenuitem) {
|
||||
return $usermenuitem->title === get_string('language');
|
||||
});
|
||||
if ($withlang) { // If multiple languages are installed.
|
||||
// Assert that the language menu exists within the user menu.
|
||||
$this->assertNotEmpty($usermenulang);
|
||||
} else { // If the aren't any additional installed languages.
|
||||
$this->assertEmpty($usermenulang);
|
||||
}
|
||||
} else { // Otherwise assert that the user menu does not contain any items.
|
||||
$this->assertArrayNotHasKey('items', $data['user']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for the test_primary_export function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_primary_export_provider(): array {
|
||||
return [
|
||||
"Export the menu data when: custom menu exists; multiple langs installed; user is not logged in." => [
|
||||
true, true, '', ['moremenu', 'lang', 'user']
|
||||
],
|
||||
"Export the menu data when: custom menu exists; langs not installed; user is not logged in." => [
|
||||
true, false, '', ['moremenu', 'user']
|
||||
],
|
||||
"Export the menu data when: custom menu exists; multiple langs installed; logged in as admin." => [
|
||||
true, true, 'admin', ['moremenu', 'user']
|
||||
],
|
||||
"Export the menu data when: custom menu exists; langs not installed; logged in as admin." => [
|
||||
true, false, 'admin', ['moremenu', 'user']
|
||||
],
|
||||
"Export the menu data when: custom menu exists; multiple langs installed; logged in as guest." => [
|
||||
true, true, 'guest', ['moremenu', 'lang', 'user']
|
||||
],
|
||||
"Export the menu data when: custom menu exists; langs not installed; logged in as guest." => [
|
||||
true, false, 'guest', ['moremenu', 'user']
|
||||
],
|
||||
"Export the menu data when: custom menu does not exist; multiple langs installed; logged in as guest." => [
|
||||
false, true, 'guest', ['moremenu', 'lang', 'user']
|
||||
],
|
||||
"Export the menu data when: custom menu does not exist; multiple langs installed; logged in as admin." => [
|
||||
false, true, 'admin', ['moremenu', 'user']
|
||||
],
|
||||
"Export the menu data when: custom menu does not exist; langs not installed; user is not logged in." => [
|
||||
false, false, '', ['moremenu', 'user']
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the get_lang_menu
|
||||
*
|
||||
* @dataProvider get_lang_menu_provider
|
||||
* @param bool $withadditionallangs
|
||||
* @param string $language
|
||||
* @param array $expected
|
||||
*/
|
||||
public function test_get_lang_menu(bool $withadditionallangs, string $language, array $expected) {
|
||||
global $CFG, $PAGE;
|
||||
|
||||
// Mimic multiple langs installed. To trigger responses 'get_list_of_translations'.
|
||||
// Note: The text/title of the nodes generated will be 'English(fr), English(de)' but we don't care about this.
|
||||
// We are testing whether the nodes gets generated when the lang menu is available.
|
||||
if ($withadditionallangs) {
|
||||
mkdir("$CFG->dataroot/lang/de", 0777, true);
|
||||
mkdir("$CFG->dataroot/lang/fr", 0777, true);
|
||||
// Ensure the new langs are picked up and not taken from the cache.
|
||||
$stringmanager = get_string_manager();
|
||||
$stringmanager->reset_caches(true);
|
||||
}
|
||||
|
||||
force_current_language($language);
|
||||
|
||||
$output = new primary($PAGE);
|
||||
$method = new ReflectionMethod('core\navigation\output\primary', 'get_lang_menu');
|
||||
$method->setAccessible(true);
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
|
||||
$response = $method->invoke($output, $renderer);
|
||||
|
||||
if ($withadditionallangs) { // If there are multiple languages installed.
|
||||
// Assert that the title of the language menu matches the expected one.
|
||||
$this->assertEquals($expected['title'], $response['title']);
|
||||
// Assert that the number of language menu items matches the number of the expected items.
|
||||
$this->assertEquals(count($expected['items']), count($response['items']));
|
||||
foreach ($expected['items'] as $expecteditem) {
|
||||
// We need to manually generate the url key and its value in the expected item array as this cannot
|
||||
// be done in the data provider due to the change of the state of $PAGE.
|
||||
$expecteditem['url'] = $expecteditem['isactive'] ? new \moodle_url('#') :
|
||||
new \moodle_url($PAGE->url, ['lang' => $expecteditem['lang']]);
|
||||
// The lang value is only used to generate the url, so this key can be removed.
|
||||
unset($expecteditem['lang']);
|
||||
|
||||
// Assert that the given expected item exists in the returned items.
|
||||
$this->assertTrue(in_array($expecteditem, $response['items']));
|
||||
}
|
||||
} else { // No multiple languages.
|
||||
$this->assertEquals($expected, $response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for test_get_lang_menu
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_lang_menu_provider(): array {
|
||||
return [
|
||||
'Lang menu with only the current language' => [
|
||||
false, 'en', []
|
||||
],
|
||||
'Lang menu with only multiple languages installed' => [
|
||||
true, 'en', [
|
||||
'title' => 'English (en)',
|
||||
'items' => [
|
||||
[
|
||||
'title' => 'English (en)',
|
||||
'text' => 'English (en)',
|
||||
'link' => true,
|
||||
'isactive' => true,
|
||||
'lang' => 'en'
|
||||
],
|
||||
[
|
||||
'title' => 'English (de)',
|
||||
'text' => 'English (de)',
|
||||
'link' => true,
|
||||
'isactive' => false,
|
||||
'lang' => 'de'
|
||||
],
|
||||
|
||||
[
|
||||
'title' => 'English (fr)',
|
||||
'text' => 'English (fr)',
|
||||
'link' => true,
|
||||
'isactive' => false,
|
||||
'lang' => 'fr'
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'Lang menu with only multiple languages installed and other than EN set active.' => [
|
||||
true, 'de', [
|
||||
'title' => 'English (de)',
|
||||
'items' => [
|
||||
[
|
||||
'title' => 'English (en)',
|
||||
'text' => 'English (en)',
|
||||
'link' => true,
|
||||
'isactive' => false,
|
||||
'lang' => 'en'
|
||||
],
|
||||
[
|
||||
'title' => 'English (de)',
|
||||
'text' => 'English (de)',
|
||||
'link' => true,
|
||||
'isactive' => true,
|
||||
'lang' => 'de'
|
||||
],
|
||||
[
|
||||
'title' => 'English (fr)',
|
||||
'text' => 'English (fr)',
|
||||
'link' => true,
|
||||
'isactive' => false,
|
||||
'lang' => 'fr'
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the custom menu getter to confirm the nodes gets generated and are returned correctly.
|
||||
*
|
||||
* @dataProvider custom_menu_provider
|
||||
* @param string $config
|
||||
* @param array $expected
|
||||
*/
|
||||
public function test_get_custom_menu(string $config, array $expected) {
|
||||
global $CFG, $PAGE;
|
||||
$CFG->custommenuitems = $config;
|
||||
$output = new primary($PAGE);
|
||||
$method = new ReflectionMethod('core\navigation\output\primary', 'get_custom_menu');
|
||||
$method->setAccessible(true);
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$this->assertEquals($expected, $method->invoke($output, $renderer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for test_get_custom_menu
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function custom_menu_provider(): array {
|
||||
return [
|
||||
'Simple custom menu' => [
|
||||
"Course search|/course/search.php
|
||||
Google|https://google.com.au/
|
||||
Netflix|https://netflix.com/au", [
|
||||
(object) [
|
||||
'text' => 'Course search',
|
||||
'url' => 'https://www.example.com/moodle/course/search.php',
|
||||
'title' => '',
|
||||
'sort' => 1,
|
||||
'children' => [],
|
||||
'haschildren' => false,
|
||||
],
|
||||
(object) [
|
||||
'text' => 'Google',
|
||||
'url' => 'https://google.com.au/',
|
||||
'title' => '',
|
||||
'sort' => 2,
|
||||
'children' => [],
|
||||
'haschildren' => false,
|
||||
],
|
||||
(object) [
|
||||
'text' => 'Netflix',
|
||||
'url' => 'https://netflix.com/au',
|
||||
'title' => '',
|
||||
'sort' => 3,
|
||||
'children' => [],
|
||||
'haschildren' => false,
|
||||
],
|
||||
]
|
||||
],
|
||||
'Complex, nested custom menu' => [
|
||||
"Moodle community|http://moodle.org
|
||||
-Moodle free support|http://moodle.org/support
|
||||
-Moodle development|http://moodle.org/development
|
||||
--Moodle Tracker|http://tracker.moodle.org
|
||||
--Moodle Docs|https://docs.moodle.org
|
||||
-Moodle News|http://moodle.org/news
|
||||
Moodle company
|
||||
-Moodle commercial hosting|http://moodle.com/hosting
|
||||
-Moodle commercial support|http://moodle.com/support", [
|
||||
(object) [
|
||||
'text' => 'Moodle community',
|
||||
'url' => 'http://moodle.org',
|
||||
'title' => '',
|
||||
'sort' => 1,
|
||||
'children' => [
|
||||
(object) [
|
||||
'text' => 'Moodle free support',
|
||||
'url' => 'http://moodle.org/support',
|
||||
'title' => '',
|
||||
'sort' => 2,
|
||||
'children' => [],
|
||||
'haschildren' => false,
|
||||
],
|
||||
(object) [
|
||||
'text' => 'Moodle development',
|
||||
'url' => 'http://moodle.org/development',
|
||||
'title' => '',
|
||||
'sort' => 3,
|
||||
'children' => [
|
||||
(object) [
|
||||
'text' => 'Moodle Tracker',
|
||||
'url' => 'http://tracker.moodle.org',
|
||||
'title' => '',
|
||||
'sort' => 4,
|
||||
'children' => [],
|
||||
'haschildren' => false,
|
||||
],
|
||||
(object) [
|
||||
'text' => 'Moodle Docs',
|
||||
'url' => 'https://docs.moodle.org',
|
||||
'title' => '',
|
||||
'sort' => 5,
|
||||
'children' => [],
|
||||
'haschildren' => false,
|
||||
],
|
||||
],
|
||||
'haschildren' => true,
|
||||
],
|
||||
(object) [
|
||||
'text' => 'Moodle News',
|
||||
'url' => 'http://moodle.org/news',
|
||||
'title' => '',
|
||||
'sort' => 6,
|
||||
'children' => [],
|
||||
'haschildren' => false,
|
||||
],
|
||||
],
|
||||
'haschildren' => true,
|
||||
],
|
||||
(object) [
|
||||
'text' => 'Moodle company',
|
||||
'url' => null,
|
||||
'title' => '',
|
||||
'sort' => 7,
|
||||
'children' => [
|
||||
(object) [
|
||||
'text' => 'Moodle commercial hosting',
|
||||
'url' => 'http://moodle.com/hosting',
|
||||
'title' => '',
|
||||
'sort' => 8,
|
||||
'children' => [],
|
||||
'haschildren' => false,
|
||||
],
|
||||
(object) [
|
||||
'text' => 'Moodle commercial support',
|
||||
'url' => 'http://moodle.com/support',
|
||||
'title' => '',
|
||||
'sort' => 9,
|
||||
'children' => [],
|
||||
'haschildren' => false,
|
||||
],
|
||||
],
|
||||
'haschildren' => true,
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -58,9 +58,9 @@ class primary_test extends \advanced_testcase {
|
||||
*/
|
||||
public function test_setting_initialise_provider() {
|
||||
return [
|
||||
'Testing as a guest user' => ['guest', ['home', 'courses']],
|
||||
'Testing as an admin' => ['admin', ['home', 'myhome', 'courses', 'siteadminnode']],
|
||||
'Testing as a regular user' => ['user', ['home', 'myhome', 'courses']]
|
||||
'Testing as a guest user' => ['guest', ['courses']],
|
||||
'Testing as an admin' => ['admin', ['myhome', 'courses', 'siteadminnode']],
|
||||
'Testing as a regular user' => ['user', ['myhome', 'courses']]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class secondary_test extends \advanced_testcase {
|
||||
$assign = $this->getDataGenerator()->create_module('assign', ['course' => $pagecourse->id]);
|
||||
$cm = get_coursemodule_from_id('assign', $assign->cmid);
|
||||
$contextrecord = \context_module::instance($cm->id);
|
||||
$pageurl = new \moodle_url('/mod/assign/view.php', ['id' => $cm->instance]);
|
||||
$pageurl = new \moodle_url('/mod/assign/view.php', ['id' => $cm->id]);
|
||||
$PAGE->set_cm($cm);
|
||||
break;
|
||||
case 'system':
|
||||
@@ -135,7 +135,131 @@ class secondary_test extends \advanced_testcase {
|
||||
return [
|
||||
'Testing in a course context' => ['course', 'coursehome', 'courseheader', 'Course page'],
|
||||
'Testing in a module context' => ['module', 'modulepage', 'activityheader', 'Activity'],
|
||||
'Testing in a site admin' => ['system', 'siteadminnode', 'menu', 'Site administration'],
|
||||
'Testing in a site admin' => ['system', 'siteadminnode', 'homeheader', 'Site administration'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the force_nodes_into_more_menu method.
|
||||
*
|
||||
* @param array $secondarynavnodesdata The array which contains the data used to generate the secondary navigation
|
||||
* @param array $defaultmoremenunodes The array containing the keys of the navigation nodes which should be added
|
||||
* to the "more" menu by default
|
||||
* @param array $expecedmoremenunodes The array containing the keys of the expected navigation nodes which are
|
||||
* forced into the "more" menu
|
||||
* @dataProvider test_force_nodes_into_more_menu_provider
|
||||
*/
|
||||
public function test_force_nodes_into_more_menu(array $secondarynavnodesdata, array $defaultmoremenunodes,
|
||||
array $expecedmoremenunodes) {
|
||||
global $PAGE;
|
||||
|
||||
// Create a dummy secondary navigation.
|
||||
$secondary = new secondary($PAGE);
|
||||
foreach ($secondarynavnodesdata as $nodedata) {
|
||||
$secondary->add($nodedata['text'], '#', secondary::TYPE_SETTING, null, $nodedata['key']);
|
||||
}
|
||||
|
||||
$method = new ReflectionMethod('core\navigation\views\secondary', 'force_nodes_into_more_menu');
|
||||
$method->setAccessible(true);
|
||||
$method->invoke($secondary, $defaultmoremenunodes);
|
||||
|
||||
$actualmoremenunodes = [];
|
||||
foreach ($secondary->children as $node) {
|
||||
if ($node->forceintomoremenu) {
|
||||
$actualmoremenunodes[] = $node->key;
|
||||
}
|
||||
}
|
||||
// Assert that the actual nodes forced into the "more" menu matches the expected ones.
|
||||
$this->assertEquals($expecedmoremenunodes, $actualmoremenunodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the test_force_nodes_into_more_menu function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_force_nodes_into_more_menu_provider(): array {
|
||||
return [
|
||||
'The total number of navigation nodes exceeds the max display limit (5); ' .
|
||||
'navnode2 and navnode4 are forced into "more" menu by default.' =>
|
||||
[
|
||||
[
|
||||
[ 'text' => 'Navigation node 1', 'key' => 'navnode1'],
|
||||
[ 'text' => 'Navigation node 2', 'key' => 'navnode2'],
|
||||
[ 'text' => 'Navigation node 3', 'key' => 'navnode3'],
|
||||
[ 'text' => 'Navigation node 4', 'key' => 'navnode4'],
|
||||
[ 'text' => 'Navigation node 5', 'key' => 'navnode5'],
|
||||
[ 'text' => 'Navigation node 6', 'key' => 'navnode6'],
|
||||
[ 'text' => 'Navigation node 7', 'key' => 'navnode7'],
|
||||
[ 'text' => 'Navigation node 8', 'key' => 'navnode8'],
|
||||
[ 'text' => 'Navigation node 9', 'key' => 'navnode9'],
|
||||
],
|
||||
[
|
||||
'navnode2',
|
||||
'navnode4',
|
||||
],
|
||||
[
|
||||
'navnode2',
|
||||
'navnode4',
|
||||
'navnode8',
|
||||
'navnode9',
|
||||
],
|
||||
],
|
||||
'The total number of navigation nodes does not exceed the max display limit (5); ' .
|
||||
'navnode2 and navnode4 are forced into "more" menu by default.' =>
|
||||
[
|
||||
[
|
||||
[ 'text' => 'Navigation node 1', 'key' => 'navnode1'],
|
||||
[ 'text' => 'Navigation node 2', 'key' => 'navnode2'],
|
||||
[ 'text' => 'Navigation node 3', 'key' => 'navnode3'],
|
||||
[ 'text' => 'Navigation node 4', 'key' => 'navnode4'],
|
||||
[ 'text' => 'Navigation node 5', 'key' => 'navnode5'],
|
||||
],
|
||||
[
|
||||
'navnode2',
|
||||
'navnode4',
|
||||
],
|
||||
[
|
||||
'navnode2',
|
||||
'navnode4',
|
||||
],
|
||||
],
|
||||
'The total number of navigation nodes exceeds the max display limit (5); ' .
|
||||
'no forced navigation nodes into "more" menu by default.' =>
|
||||
[
|
||||
[
|
||||
[ 'text' => 'Navigation node 1', 'key' => 'navnode1'],
|
||||
[ 'text' => 'Navigation node 2', 'key' => 'navnode2'],
|
||||
[ 'text' => 'Navigation node 3', 'key' => 'navnode3'],
|
||||
[ 'text' => 'Navigation node 4', 'key' => 'navnode4'],
|
||||
[ 'text' => 'Navigation node 5', 'key' => 'navnode5'],
|
||||
[ 'text' => 'Navigation node 6', 'key' => 'navnode6'],
|
||||
[ 'text' => 'Navigation node 7', 'key' => 'navnode7'],
|
||||
[ 'text' => 'Navigation node 8', 'key' => 'navnode8'],
|
||||
],
|
||||
[],
|
||||
[
|
||||
'navnode6',
|
||||
'navnode7',
|
||||
'navnode8',
|
||||
],
|
||||
],
|
||||
'The total number of navigation nodes does not exceed the max display limit (5); ' .
|
||||
'no forced navigation nodes into "more" menu by default.' =>
|
||||
[
|
||||
[
|
||||
[ 'text' => 'Navigation node 1', 'key' => 'navnode1'],
|
||||
[ 'text' => 'Navigation node 2', 'key' => 'navnode2'],
|
||||
[ 'text' => 'Navigation node 3', 'key' => 'navnode3'],
|
||||
[ 'text' => 'Navigation node 4', 'key' => 'navnode4'],
|
||||
[ 'text' => 'Navigation node 5', 'key' => 'navnode5'],
|
||||
[ 'text' => 'Navigation node 6', 'key' => 'navnode6'],
|
||||
],
|
||||
[],
|
||||
[
|
||||
'navnode6',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,6 +545,58 @@ class core_navigationlib_testcase extends advanced_testcase {
|
||||
// Test it's empty again!
|
||||
$this->assertEquals(0, count($navigationnodecollection->get_key_list()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the set_force_into_more_menu method.
|
||||
*
|
||||
* @param bool $haschildren Whether the navigation node has children nodes
|
||||
* @param bool $forceintomoremenu Whether to force the navigation node and its children into the "more" menu
|
||||
* @dataProvider test_set_force_into_more_menu_provider
|
||||
*/
|
||||
public function test_set_force_into_more_menu(bool $haschildren, bool $forceintomoremenu) {
|
||||
// Create a navigation node.
|
||||
$node = new navigation_node(['text' => 'Navigation node', 'key' => 'navnode']);
|
||||
|
||||
// If required, add some children nodes to the navigation node.
|
||||
if ($haschildren) {
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
$node->add("Child navigation node {$i}");
|
||||
}
|
||||
}
|
||||
|
||||
$node->set_force_into_more_menu($forceintomoremenu);
|
||||
// Assert that the expected value has been assigned to the 'forceintomoremenu' property
|
||||
// in the navigation node and its children.
|
||||
$this->assertEquals($forceintomoremenu, $node->forceintomoremenu);
|
||||
foreach ($node->children as $child) {
|
||||
$this->assertEquals($forceintomoremenu, $child->forceintomoremenu);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the test_set_force_into_more_menu function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_set_force_into_more_menu_provider(): array {
|
||||
return [
|
||||
'Navigation node without any children nodes; Force into "more" menu => true.' =>
|
||||
[
|
||||
false,
|
||||
true,
|
||||
],
|
||||
'Navigation node with children nodes; Force into "more" menu => true.' =>
|
||||
[
|
||||
true,
|
||||
true,
|
||||
],
|
||||
'Navigation node with children nodes; Force into "more" menu => false.' =>
|
||||
[
|
||||
true,
|
||||
false,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace mod_assign\local\views;
|
||||
|
||||
use core\navigation\views\secondary as core_secondary;
|
||||
|
||||
/**
|
||||
* Class secondary_navigation_view.
|
||||
*
|
||||
* Custom implementation for a plugin.
|
||||
*
|
||||
* @package mod_assign
|
||||
* @category navigation
|
||||
* @copyright 2021 onwards Peter Dias
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class secondary extends core_secondary {
|
||||
protected function get_default_module_mapping(): array {
|
||||
return [
|
||||
self::TYPE_SETTING => [
|
||||
'modedit' => 1,
|
||||
"mod_{$this->page->activityname}_useroverrides" => 2, // Overrides are module specific.
|
||||
"mod_{$this->page->activityname}_groupoverrides" => 3,
|
||||
],
|
||||
self::TYPE_CUSTOM => [
|
||||
'advgrading' => 4,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4564,18 +4564,19 @@ class assign {
|
||||
$gradingoptionsform->set_data($gradingoptionsdata);
|
||||
|
||||
$actionformtext = $this->get_renderer()->render($gradingactions);
|
||||
|
||||
$currenturl = new moodle_url('/mod/assign/view.php', ['id' => $this->get_course_module()->id, 'action' => 'grading']);
|
||||
|
||||
$header = new assign_header($this->get_instance(),
|
||||
$this->get_context(),
|
||||
false,
|
||||
$this->get_course_module()->id,
|
||||
get_string('grading', 'assign'),
|
||||
$actionformtext);
|
||||
$actionformtext,
|
||||
'',
|
||||
$currenturl);
|
||||
$o .= $this->get_renderer()->render($header);
|
||||
|
||||
$currenturl = $CFG->wwwroot .
|
||||
'/mod/assign/view.php?id=' .
|
||||
$this->get_course_module()->id .
|
||||
'&action=grading';
|
||||
|
||||
$o .= groups_print_activity_menu($this->get_course_module(), $currenturl, true);
|
||||
|
||||
|
||||
@@ -671,6 +671,8 @@ class assign_header implements renderable {
|
||||
public $preface = '';
|
||||
/** @var string $postfix optional postfix (text to show after the intro) */
|
||||
public $postfix = '';
|
||||
/** @var moodle_url $subpageurl link for the subpage */
|
||||
public $subpageurl = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -688,7 +690,8 @@ class assign_header implements renderable {
|
||||
$coursemoduleid,
|
||||
$subpage='',
|
||||
$preface='',
|
||||
$postfix='') {
|
||||
$postfix='',
|
||||
moodle_url $subpageurl = null) {
|
||||
$this->assign = $assign;
|
||||
$this->context = $context;
|
||||
$this->showintro = $showintro;
|
||||
@@ -696,6 +699,7 @@ class assign_header implements renderable {
|
||||
$this->subpage = $subpage;
|
||||
$this->preface = $preface;
|
||||
$this->postfix = $postfix;
|
||||
$this->subpageurl = $subpageurl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ class mod_assign_renderer extends plugin_renderer_base {
|
||||
$o = '';
|
||||
|
||||
if ($header->subpage) {
|
||||
$this->page->navbar->add($header->subpage);
|
||||
$this->page->navbar->add($header->subpage, $header->subpageurl);
|
||||
$args = ['contextname' => $header->context->get_context_name(false, true), 'subpage' => $header->subpage];
|
||||
$title = get_string('subpagetitle', 'assign', $args);
|
||||
} else {
|
||||
|
||||
@@ -25,5 +25,5 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->component = 'mod_assign'; // Full name of the plugin (used for diagnostics).
|
||||
$plugin->version = 2021052502; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2021052503; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2021052500; // Requires this Moodle version.
|
||||
|
||||
@@ -51,7 +51,8 @@ Feature: In a book, chapters and subchapters can be rearranged
|
||||
|
||||
Scenario: Moving chapters down rearranges them properly
|
||||
Given I click on "Move chapter down \"1. Originally first chapter\"" "link"
|
||||
When I follow "Test book"
|
||||
When I am on "Course 1" course homepage
|
||||
And I follow "Test book"
|
||||
Then I should see "1. A great second chapter"
|
||||
And I should see "#2 chapter content"
|
||||
And I should see "1.1. Second chapter, subchapter 1"
|
||||
@@ -61,7 +62,8 @@ Feature: In a book, chapters and subchapters can be rearranged
|
||||
|
||||
Scenario: Moving chapters up rearranges them properly
|
||||
Given I click on "Move chapter up \"3. There aren't 2 without 3\"" "link"
|
||||
When I follow "Test book"
|
||||
When I am on "Course 1" course homepage
|
||||
And I follow "Test book"
|
||||
Then I should see "1. Originally first chapter"
|
||||
And I should see "#1 chapter content"
|
||||
And I should see "2. There aren't 2 without 3"
|
||||
@@ -71,30 +73,26 @@ Feature: In a book, chapters and subchapters can be rearranged
|
||||
|
||||
Scenario: Moving subchapters down within chapter rearranges them properly
|
||||
Given I click on "Move chapter down \"2.1. Second chapter, subchapter 1\"" "link"
|
||||
When I follow "Test book"
|
||||
Then I should see "2.1. Second chapter, subchapter 2"
|
||||
And I should see "2.2. Second chapter, subchapter 1"
|
||||
When I should see "2.1. Second chapter, subchapter 2"
|
||||
Then I should see "2.2. Second chapter, subchapter 1"
|
||||
|
||||
Scenario: Moving subchapters down out of chapter rearranges them properly
|
||||
Given I click on "Move chapter down \"2.2. Second chapter, subchapter 2\"" "link"
|
||||
When I follow "Test book"
|
||||
Then I should see "3.1. Second chapter, subchapter 2"
|
||||
And I click on "Move chapter down \"3. There aren't 2 without 3\"" "link"
|
||||
When I should see "3.1. Second chapter, subchapter 2"
|
||||
Then I click on "Move chapter down \"3. There aren't 2 without 3\"" "link"
|
||||
And I should not see "4. There aren't 2 without 3"
|
||||
And I should see "3. There aren't 2 without 3"
|
||||
And I should see "3.1. Second chapter, subchapter 2"
|
||||
|
||||
Scenario: Moving subchapters up within chapter rearranges them properly
|
||||
Given I click on "Move chapter up \"2.2. Second chapter, subchapter 2\"" "link"
|
||||
When I follow "Test book"
|
||||
Then I should see "2.1. Second chapter, subchapter 2"
|
||||
And I should see "2.2. Second chapter, subchapter 1"
|
||||
When I should see "2.1. Second chapter, subchapter 2"
|
||||
Then I should see "2.2. Second chapter, subchapter 1"
|
||||
|
||||
Scenario: Moving subchapters up out of chapter rearranges them properly
|
||||
Given I click on "Move chapter up \"2.1. Second chapter, subchapter 1\"" "link"
|
||||
When I follow "Test book"
|
||||
Then I should see "1.1. Second chapter, subchapter 1"
|
||||
And I click on "Move chapter up \"1.1. Second chapter, subchapter 1\"" "link"
|
||||
When I should see "1.1. Second chapter, subchapter 1"
|
||||
Then I click on "Move chapter up \"1.1. Second chapter, subchapter 1\"" "link"
|
||||
And I should not see "1.1. Second chapter, subchapter 1"
|
||||
And I should see "1. Second chapter, subchapter 1"
|
||||
And I should see "2. Originally first chapter"
|
||||
|
||||
@@ -46,7 +46,7 @@ class behat_mod_choice extends behat_base {
|
||||
* @return array
|
||||
*/
|
||||
public function I_choose_option_from_activity($option, $choiceactivity) {
|
||||
$this->execute("behat_navigation::i_am_on_page_instance", [$this->escape($choiceactivity), 'choice activity']);
|
||||
$this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($choiceactivity), 'choice activity']);
|
||||
|
||||
$this->execute('behat_forms::i_set_the_field_to', array( $this->escape($option), 1));
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ Feature: Saving, using and deleting feedback templates
|
||||
And I press "Use this template"
|
||||
And I should see "this is a multiple choice 1"
|
||||
And I press "Save changes"
|
||||
And I follow "Edit questions"
|
||||
And I should see "this is a multiple choice 1"
|
||||
# Make sure this template is not available in another course
|
||||
And I am on the "Learning experience course 2" "feedback activity" page
|
||||
@@ -85,7 +84,6 @@ Feature: Saving, using and deleting feedback templates
|
||||
And I press "Use this template"
|
||||
And I set the field "Append new items" to "1"
|
||||
And I press "Save changes"
|
||||
And I follow "Edit questions"
|
||||
Then "What is your favourite subject" "text" should appear before "this is a multiple choice 1" "text"
|
||||
# Import template replacing items
|
||||
And I follow "Templates"
|
||||
@@ -93,6 +91,7 @@ Feature: Saving, using and deleting feedback templates
|
||||
And I press "Use this template"
|
||||
And I set the field "Delete old items" to "1"
|
||||
And I press "Save changes"
|
||||
And I am on the "Another feedback in course 1" "feedback activity" page
|
||||
And I follow "Edit questions"
|
||||
And I should not see "What is your favourite subject"
|
||||
And I should see "this is a multiple choice 1"
|
||||
@@ -110,7 +109,6 @@ Feature: Saving, using and deleting feedback templates
|
||||
And I press "Use this template"
|
||||
Then I should see "this is a multiple choice 1"
|
||||
And I press "Save changes"
|
||||
And I follow "Edit questions"
|
||||
And I should see "this is a multiple choice 1"
|
||||
|
||||
Scenario: Teacher can delete course templates but can not delete public templates
|
||||
|
||||
@@ -139,8 +139,10 @@ $strpage = get_string("page");
|
||||
|
||||
if (!$search || $showform) {
|
||||
|
||||
$PAGE->navbar->add($strforums, new moodle_url('/mod/forum/index.php', array('id'=>$course->id)));
|
||||
$PAGE->navbar->add(get_string('advancedsearch', 'forum'));
|
||||
$url = new moodle_url('/mod/forum/index.php', array('id' => $course->id));
|
||||
$PAGE->navbar->add($strforums, $url);
|
||||
$url = new moodle_url('/mod/forum/search.php', array('id' => $course->id));
|
||||
$PAGE->navbar->add(get_string('advancedsearch', 'forum'), $url);
|
||||
|
||||
$PAGE->set_title($strsearch);
|
||||
$PAGE->set_heading($course->fullname);
|
||||
|
||||
@@ -97,7 +97,7 @@ if (data_submitted()) {
|
||||
}
|
||||
|
||||
$strsubscribers = get_string("subscribers", "forum");
|
||||
$PAGE->navbar->add($strsubscribers);
|
||||
$PAGE->navbar->add($strsubscribers, $url);
|
||||
$PAGE->set_title($strsubscribers);
|
||||
$PAGE->set_heading($COURSE->fullname);
|
||||
if (has_capability('mod/forum:managesubscriptions', $context) && \mod_forum\subscriptions::is_forcesubscribed($forum) === false) {
|
||||
|
||||
@@ -113,7 +113,7 @@ class behat_mod_forum extends behat_base {
|
||||
*/
|
||||
public function i_reply_post_from_forum_using_an_inpage_reply_with($postsubject, $forumname, TableNode $table) {
|
||||
// Navigate to forum.
|
||||
$this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($forumname), 'forum activity']);
|
||||
$this->execute("behat_navigation::go_to_breadcrumb_location", $this->escape($forumname));
|
||||
$this->execute('behat_general::click_link', $this->escape($postsubject));
|
||||
$this->execute('behat_general::click_link', get_string('reply', 'forum'));
|
||||
|
||||
@@ -132,7 +132,7 @@ class behat_mod_forum extends behat_base {
|
||||
*/
|
||||
public function i_navigate_to_post_in_forum($postsubject, $forumname) {
|
||||
// Navigate to forum discussion.
|
||||
$this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($forumname), 'forum activity']);
|
||||
$this->execute("behat_navigation::go_to_breadcrumb_location", $this->escape($forumname));
|
||||
$this->execute('behat_general::click_link', $this->escape($postsubject));
|
||||
}
|
||||
|
||||
@@ -493,7 +493,7 @@ class behat_mod_forum extends behat_base {
|
||||
*/
|
||||
protected function add_new_discussion_inline($forumname, TableNode $table, $buttonstr) {
|
||||
// Navigate to forum.
|
||||
$this->execute('behat_navigation::i_am_on_page_instance', [$this->escape($forumname), 'forum activity']);
|
||||
$this->execute("behat_navigation::go_to_breadcrumb_location", $this->escape($forumname));
|
||||
$this->execute('behat_general::click_link', $buttonstr);
|
||||
$this->fill_new_discussion_form($table);
|
||||
}
|
||||
|
||||
@@ -341,7 +341,6 @@ Feature: A user can control their own subscription preferences for a discussion
|
||||
And I follow "You are not subscribed to this discussion. Click to subscribe"
|
||||
And I should see "Student One will be notified of new posts in 'Test post subject one' of 'Test forum name'"
|
||||
And "Unsubscribe from this discussion" "checkbox" should exist in the "Test post subject one" "table_row"
|
||||
And I follow "Test forum name"
|
||||
And I navigate to "Subscribe to this forum" in current page administration
|
||||
And I should see "Student One will be notified of new posts in 'Test forum name'"
|
||||
And "Unsubscribe from this forum" "link" should exist in current page administration
|
||||
|
||||
@@ -57,6 +57,7 @@ Feature: Teachers can edit or delete any forum post
|
||||
And I should see "Edited by Teacher 1 - original submission"
|
||||
|
||||
Scenario: A student can't edit or delete another user's posts
|
||||
When I follow "Teacher post subject"
|
||||
When I follow "Test forum name"
|
||||
And I follow "Teacher post subject"
|
||||
Then I should not see "Edit" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' forumpost ')][contains(., 'Teacher post subject')]" "xpath_element"
|
||||
And I should not see "Delete" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' forumpost ')][contains(., 'Teacher post subject')]" "xpath_element"
|
||||
|
||||
@@ -36,7 +36,8 @@ Feature: Forum posts can be replied to in private
|
||||
| Reply privately | 1 |
|
||||
|
||||
Scenario: As a teacher I can see my own response
|
||||
Given I follow "Answers to the homework"
|
||||
Given I follow "Study discussions"
|
||||
And I follow "Answers to the homework"
|
||||
Then I should see "How about you and I have a meeting after class about plagiarism?"
|
||||
|
||||
Scenario: As a fellow teacher I can see the other teacher's response
|
||||
|
||||
@@ -61,7 +61,7 @@ class behat_mod_glossary extends behat_base {
|
||||
*/
|
||||
public function i_add_a_glossary_entries_category_named($categoryname) {
|
||||
|
||||
$this->execute("behat_general::click_link", get_string('categoryview', 'mod_glossary'));
|
||||
$this->execute("behat_navigation::go_to_breadcrumb_location", get_string('categoryview', 'mod_glossary'));
|
||||
|
||||
$this->execute("behat_forms::press_button", get_string('editcategories', 'mod_glossary'));
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ Feature: A teacher can choose whether glossary entries require approval
|
||||
And I follow "Waiting approval"
|
||||
Then I should see "(this entry is currently hidden)"
|
||||
And I follow "Approve"
|
||||
And I click on "Test glossary name" "link" in the "page-header" "region"
|
||||
And I am on the "Test glossary name" "glossary activity" page
|
||||
Then I should see "Concept definition"
|
||||
And I log out
|
||||
# Check that the entry can now be viewed by students.
|
||||
|
||||
@@ -89,6 +89,7 @@ Feature: In a lesson activity a student should
|
||||
And I should see "Second page contents"
|
||||
And I press "Next page"
|
||||
And I should see "Third page contents"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson name"
|
||||
And I should see "You have seen more than one page of this lesson already."
|
||||
And I should see "Do you want to start at the last page you saw?"
|
||||
@@ -98,6 +99,7 @@ Feature: In a lesson activity a student should
|
||||
And I wait "1" seconds
|
||||
And I press "Next page"
|
||||
And I should see "Paper is made from trees."
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson name"
|
||||
And I should see "You have seen more than one page of this lesson already."
|
||||
And I should see "Do you want to start at the last page you saw?"
|
||||
@@ -108,6 +110,7 @@ Feature: In a lesson activity a student should
|
||||
And I press "Submit"
|
||||
And I press "Continue"
|
||||
And I should see "Kermit is a frog"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson name"
|
||||
And I should see "You have seen more than one page of this lesson already."
|
||||
And I should see "Do you want to start at the last page you saw?"
|
||||
@@ -165,6 +168,7 @@ Feature: In a lesson activity a student should
|
||||
And I wait "1" seconds
|
||||
And I press "Next page"
|
||||
And I should see "Third page contents"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson name"
|
||||
Then I should see "You have seen more than one page of this lesson already."
|
||||
And I should see "Do you want to start at the last page you saw?"
|
||||
@@ -286,6 +290,7 @@ Feature: In a lesson activity a student should
|
||||
And I press "Submit"
|
||||
And I press "Continue"
|
||||
And I should see "2+2=4"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson name"
|
||||
And I should see "You have seen more than one page of this lesson already."
|
||||
Then I should see "Do you want to start at the last page you saw?"
|
||||
@@ -298,6 +303,7 @@ Feature: In a lesson activity a student should
|
||||
And I press "Submit"
|
||||
And I press "Continue"
|
||||
And I should see "Second content page"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson name"
|
||||
And I should see "You have seen more than one page of this lesson already."
|
||||
And I should see "Do you want to start at the last page you saw?"
|
||||
@@ -399,6 +405,7 @@ Feature: In a lesson activity a student should
|
||||
And I press "Submit"
|
||||
And I press "Continue"
|
||||
And I should see "2+2=4"
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test lesson name"
|
||||
Then I should see "You have seen more than one page of this lesson already."
|
||||
And I should see "Do you want to start at the last page you saw?"
|
||||
|
||||
@@ -27,13 +27,12 @@ Feature: Restoring Moodle 2 backup restores LTI configuration
|
||||
And I navigate to "Plugins > Activity modules > External tool > Manage tools" in site administration
|
||||
And "This tool has not yet been used" "text" should exist in the "//div[contains(@id,'tool-card-container') and contains(., 'My site tool')]" "xpath_element"
|
||||
And I am on site homepage
|
||||
And I follow "Course 1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I turn editing mode on
|
||||
And I add a "External tool" to section "1" and I fill the form with:
|
||||
| Activity name | My LTI module |
|
||||
| Preconfigured tool | My site tool |
|
||||
| Launch container | Embed |
|
||||
And I follow "Course 1"
|
||||
And I should see "My LTI module"
|
||||
And I backup "Course 1" course using this options:
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
|
||||
@@ -54,6 +54,7 @@ Feature: View activity completion in the quiz activity
|
||||
And user "student1" has attempted "Test quiz name" with responses:
|
||||
| slot | response |
|
||||
| 1 | False |
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "Test quiz name"
|
||||
And the "View" completion condition of "Test quiz name" is displayed as "done"
|
||||
And the "Make attempts: 1" completion condition of "Test quiz name" is displayed as "done"
|
||||
|
||||
@@ -46,7 +46,7 @@ require_capability('mod/workshop:allocate', $context);
|
||||
|
||||
$PAGE->set_title($workshop->name);
|
||||
$PAGE->set_heading($course->fullname);
|
||||
$PAGE->navbar->add(get_string('allocation', 'workshop'));
|
||||
$PAGE->navbar->add(get_string('allocation', 'workshop'), $workshop->allocation_url($method));
|
||||
|
||||
$allocator = $workshop->allocator_instance($method);
|
||||
$initresult = $allocator->init();
|
||||
|
||||
@@ -87,7 +87,7 @@ class behat_workshopallocation_manual extends behat_base {
|
||||
* @param TableNode $table should have one column with title 'Reviewer' and another with title 'Participant' (or 'Reviewee')
|
||||
*/
|
||||
public function i_allocate_submissions_in_workshop_as($workshopname, TableNode $table) {
|
||||
$this->execute("behat_general::i_click_on_in_the", [$this->escape($workshopname), 'link', 'page', 'region']);
|
||||
$this->execute("behat_navigation::go_to_breadcrumb_location", $workshopname);
|
||||
$this->execute('behat_navigation::i_navigate_to_in_current_page_administration', get_string('allocate', 'workshop'));
|
||||
$rows = $table->getRows();
|
||||
$reviewer = $participant = null;
|
||||
|
||||
@@ -52,7 +52,7 @@ class behat_mod_workshop extends behat_base {
|
||||
$xpath = "//*[@class='userplan']/descendant::div[./span[contains(.,$phaseliteral)]]";
|
||||
$continue = $this->escape(get_string('continue'));
|
||||
|
||||
$this->execute("behat_general::i_click_on_in_the", [$this->escape($workshopname), 'link', 'page', 'region']);
|
||||
$this->execute("behat_navigation::go_to_breadcrumb_location", $workshopname);
|
||||
|
||||
$this->execute('behat_general::i_click_on_in_the',
|
||||
array('a.action-icon', "css_element", $this->escape($xpath), "xpath_element")
|
||||
@@ -73,7 +73,7 @@ class behat_mod_workshop extends behat_base {
|
||||
$savechanges = $this->escape(get_string('savechanges'));
|
||||
$xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' ownsubmission ')]/descendant::*[@type='submit']";
|
||||
|
||||
$this->execute("behat_navigation::i_am_on_page_instance", [$this->escape($workshopname), 'workshop activity']);
|
||||
$this->execute("behat_navigation::i_am_on_page_instance", [$workshopname, 'workshop activity']);
|
||||
|
||||
$this->execute("behat_general::i_click_on", array($xpath, "xpath_element"));
|
||||
|
||||
@@ -116,7 +116,7 @@ class behat_mod_workshop extends behat_base {
|
||||
$assess = $this->escape(get_string('assess', 'workshop'));
|
||||
$saveandclose = $this->escape(get_string('saveandclose', 'workshop'));
|
||||
|
||||
$this->execute("behat_navigation::i_am_on_page_instance", [$workshopname, 'workshop activity']);
|
||||
$this->execute("behat_navigation::go_to_breadcrumb_location", $workshopname);
|
||||
|
||||
$this->execute('behat_general::i_click_on_in_the',
|
||||
array($assess, "button", $xpath, "xpath_element")
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>
|
||||
|
After Width: | Height: | Size: 312 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
|
||||
|
After Width: | Height: | Size: 314 B |
@@ -35,14 +35,14 @@ Feature: View the user page for the outline report
|
||||
And I am on "Course 1" course homepage
|
||||
# We want to view this multiple times, to make sure the count is working.
|
||||
And I follow "Folder name"
|
||||
And I follow "Folder name"
|
||||
And I follow "Folder name"
|
||||
And I follow "Folder name"
|
||||
And I am on "Course 1" course homepage
|
||||
And I reload the page
|
||||
And I reload the page
|
||||
And I reload the page
|
||||
# We want to view this multiple times, to make sure the count is working.
|
||||
And I am on "Course 1" course homepage
|
||||
And I follow "URL name"
|
||||
And I follow "URL name"
|
||||
And I follow "URL name"
|
||||
And I reload the page
|
||||
And I reload the page
|
||||
And I follow "Profile" in the user menu
|
||||
And I click on "Course 1" "link" in the "region-main" "region"
|
||||
When I follow "Outline report"
|
||||
@@ -63,14 +63,14 @@ Feature: View the user page for the outline report
|
||||
And I am on "Course 1" course homepage
|
||||
# We want to view this multiple times, to make sure the count is working.
|
||||
And I follow "Folder name"
|
||||
And I follow "Folder name"
|
||||
And I follow "Folder name"
|
||||
And I follow "Folder name"
|
||||
And I reload the page
|
||||
And I reload the page
|
||||
And I reload the page
|
||||
And I am on "Course 1" course homepage
|
||||
# We want to view this multiple times, to make sure the count is working.
|
||||
And I follow "URL name"
|
||||
And I follow "URL name"
|
||||
And I follow "URL name"
|
||||
And I reload the page
|
||||
And I reload the page
|
||||
And I follow "Profile" in the user menu
|
||||
And I click on "Course 1" "link" in the "region-main" "region"
|
||||
When I follow "Outline report"
|
||||
@@ -93,14 +93,14 @@ Feature: View the user page for the outline report
|
||||
And I am on "Course 1" course homepage
|
||||
# We want to view this multiple times, to make sure the count is working.
|
||||
And I follow "Folder name"
|
||||
And I follow "Folder name"
|
||||
And I follow "Folder name"
|
||||
And I follow "Folder name"
|
||||
And I reload the page
|
||||
And I reload the page
|
||||
And I reload the page
|
||||
And I am on "Course 1" course homepage
|
||||
# We want to view this multiple times, to make sure the count is working.
|
||||
And I follow "URL name"
|
||||
And I follow "URL name"
|
||||
And I follow "URL name"
|
||||
And I reload the page
|
||||
And I reload the page
|
||||
And I follow "Profile" in the user menu
|
||||
And I click on "Course 1" "link" in the "region-main" "region"
|
||||
When I follow "Outline report"
|
||||
|
||||
+3
-2
@@ -78,7 +78,8 @@ if ($ctx && ($context = context::instance_by_id($ctx, IGNORE_MISSING)) && $conte
|
||||
|
||||
$tagcollid = $tag->tagcollid;
|
||||
|
||||
$PAGE->set_url($tag->get_view_url($exclusivemode, $fromctx, $ctx, $rec));
|
||||
$pageurl = $tag->get_view_url($exclusivemode, $fromctx, $ctx, $rec);
|
||||
$PAGE->set_url($pageurl);
|
||||
$PAGE->set_subpage($tag->id);
|
||||
$tagnode = $PAGE->navigation->find('tags', null);
|
||||
$tagnode->make_active();
|
||||
@@ -97,7 +98,7 @@ if ($PAGE->user_allowed_editing()) {
|
||||
$buttons .= $OUTPUT->edit_button(clone($PAGE->url));
|
||||
}
|
||||
|
||||
$PAGE->navbar->add($tagname);
|
||||
$PAGE->navbar->add($tagname, $pageurl);
|
||||
$PAGE->set_title(get_string('tag', 'tag') .' - '. $tag->get_display_name());
|
||||
$PAGE->set_heading($COURSE->fullname);
|
||||
$PAGE->set_button($buttons);
|
||||
|
||||
@@ -86,7 +86,7 @@ Feature: Managers can create and manage tag collections
|
||||
And I should see "Tag3"
|
||||
And I should not see "Tag1"
|
||||
And I should not see "Tag2"
|
||||
And I follow "Manage tags"
|
||||
And I navigate to "Appearance > Manage tags" in site administration
|
||||
And I follow "Default collection"
|
||||
# Tag "Swimming" was not standard and was moved completely.
|
||||
And I should not see "Swimming"
|
||||
|
||||
@@ -61,6 +61,7 @@ Feature: Manager is able to delete tags
|
||||
And I press "Yes"
|
||||
And I should see "Tag(s) deleted"
|
||||
And I should not see "Dog"
|
||||
And I navigate to "Appearance > Manage tags" in site administration
|
||||
And I follow "Default collection"
|
||||
And I should not see "Dog"
|
||||
And I follow "Cat"
|
||||
@@ -94,6 +95,7 @@ Feature: Manager is able to delete tags
|
||||
And I should see "Tag(s) deleted"
|
||||
And I should not see "Dog"
|
||||
And I should not see "Neverusedtag"
|
||||
And I navigate to "Appearance > Manage tags" in site administration
|
||||
And I follow "Default collection"
|
||||
And I should not see "Dog"
|
||||
And I should not see "Neverusedtag"
|
||||
|
||||
@@ -123,7 +123,6 @@ Feature: Users can edit tags to add description or rename
|
||||
| Related tags | Dog, Turtle,Fish |
|
||||
| Standard | 0 |
|
||||
And I press "Update"
|
||||
Then "Default collection" "link" should exist in the ".breadcrumb" "css_element"
|
||||
And I follow "Kitten"
|
||||
And "Description of tag 1" "text" should exist in the ".tag-description" "css_element"
|
||||
And I should see "Related tags:" in the ".tag_list" "css_element"
|
||||
@@ -143,12 +142,10 @@ Feature: Users can edit tags to add description or rename
|
||||
And I set the following fields to these values:
|
||||
| Tag name | Kitten |
|
||||
And I press "Update"
|
||||
Then "Default collection" "text" should exist in the ".breadcrumb" "css_element"
|
||||
And I click on "Edit this tag" "link" in the "Kitten" "table_row"
|
||||
And I set the following fields to these values:
|
||||
| Tag name | KITTEN |
|
||||
And I press "Update"
|
||||
And "Default collection" "text" should exist in the ".breadcrumb" "css_element"
|
||||
And I should see "KITTEN"
|
||||
And I should not see "Kitten"
|
||||
|
||||
@@ -161,6 +158,7 @@ Feature: Users can edit tags to add description or rename
|
||||
And I set the field "Edit tag name" in the "Cat" "table_row" to "Kitten"
|
||||
Then I should not see "Cat"
|
||||
And "New name for tag" "field" should not exist
|
||||
And I navigate to "Appearance > Manage tags" in site administration
|
||||
And I follow "Default collection"
|
||||
And I should see "Kitten"
|
||||
And I should not see "Cat"
|
||||
@@ -172,6 +170,7 @@ Feature: Users can edit tags to add description or rename
|
||||
And I should see "Turtle"
|
||||
And I should see "Dog"
|
||||
And I should not see "DOG"
|
||||
And I navigate to "Appearance > Manage tags" in site administration
|
||||
And I follow "Default collection"
|
||||
And I should see "Turtle"
|
||||
And I should see "Dog"
|
||||
@@ -183,6 +182,7 @@ Feature: Users can edit tags to add description or rename
|
||||
And "New name for tag" "field" should not exist
|
||||
And I should see "Turtle"
|
||||
And I should not see "Penguin"
|
||||
And I navigate to "Appearance > Manage tags" in site administration
|
||||
And I follow "Default collection"
|
||||
And I should see "Turtle"
|
||||
And I should not see "Penguin"
|
||||
|
||||
@@ -78,6 +78,7 @@ Feature: Users can flag tags and manager can reset flags
|
||||
And "(1)" "text" should exist in the "//tr[contains(.,'Nicetag')]//td[contains(@class,'col-flag')]" "xpath_element"
|
||||
And "(" "text" should not exist in the "//tr[contains(.,'Badtag')]//td[contains(@class,'col-flag')]" "xpath_element"
|
||||
And "(" "text" should not exist in the "//tr[contains(.,'Neverusedtag')]//td[contains(@class,'col-flag')]" "xpath_element"
|
||||
And I navigate to "Appearance > Manage tags" in site administration
|
||||
And I follow "Default collection"
|
||||
And "Nicetag" "link" should appear before "Sweartag" "link"
|
||||
And "Sweartag" "link" should appear before "Badtag" "link"
|
||||
|
||||
@@ -63,6 +63,7 @@ Feature: Manager can add standard tags and change the tag type of existing tags
|
||||
And "Remove from standard tags" "link" should exist in the "Tag1" "table_row"
|
||||
And "Make standard" "link" should exist in the "Tag2" "table_row"
|
||||
And "Make standard" "link" should exist in the "Tag3" "table_row"
|
||||
And I navigate to "Appearance > Manage tags" in site administration
|
||||
And I follow "Default collection"
|
||||
And "Make standard" "link" should exist in the "Tag0" "table_row"
|
||||
And "Remove from standard tags" "link" should exist in the "Tag1" "table_row"
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace theme_boost;
|
||||
|
||||
/**
|
||||
* Creates a navbar for boost that allows easy control of the navbar items.
|
||||
*
|
||||
* @package theme_boost
|
||||
* @copyright 2021 Adrian Greeve <adrian@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class boostnavbar implements \renderable {
|
||||
|
||||
/** @var array The individual items of the navbar. */
|
||||
protected $items = [];
|
||||
|
||||
/**
|
||||
* Takes a navbar object and picks the necessary parts for display.
|
||||
*
|
||||
* @param \navbar $navbar The navigation bar.
|
||||
*/
|
||||
public function __construct(\navbar $navbar) {
|
||||
foreach ($navbar->get_items() as $item) {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
$this->prepare_nodes_for_boost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the navigation nodes for use with boost.
|
||||
*/
|
||||
protected function prepare_nodes_for_boost(): void {
|
||||
// Don't display the navbar if we are in the site navigation.
|
||||
if (!is_null($this->get_item('root'))) {
|
||||
$this->clear_items();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->remove('myhome'); // Dashboard.
|
||||
$this->remove('home');
|
||||
|
||||
// Set the designated one path for courses.
|
||||
$mycoursesnode = $this->get_item('mycourses');
|
||||
if (!is_null($mycoursesnode)) {
|
||||
// TODO: Once MDL-70801 lands point this to the new page.
|
||||
$url = new \moodle_url('/course/');
|
||||
$mycoursesnode->action = $url;
|
||||
$mycoursesnode->text = get_string('courses');
|
||||
}
|
||||
|
||||
$this->remove_no_link_items();
|
||||
|
||||
// Don't display the navbar if there is only one item. Apparently this is bad UX design.
|
||||
if ($this->item_count() <= 1) {
|
||||
$this->clear_items();
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure that the last item is not a link. Not sure if this is always a good idea.
|
||||
$this->remove_last_item_action();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the boostnavbaritem elements.
|
||||
*
|
||||
* @return boostnavbaritem[] Boost navbar items.
|
||||
*/
|
||||
public function get_items(): array {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all navigation items out of this boost navbar
|
||||
*/
|
||||
protected function clear_items(): void {
|
||||
$this->items = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a single navbar item.
|
||||
*
|
||||
* @param string|int $key The identifier of the navbar item to return.
|
||||
* @return \breadcrumb_navigation_node|null The navbar item.
|
||||
*/
|
||||
protected function get_item($key): ?\breadcrumb_navigation_node {
|
||||
foreach ($this->items as $item) {
|
||||
if ($key === $item->key) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts all of the navbar items.
|
||||
*
|
||||
* @return int How many navbar items there are.
|
||||
*/
|
||||
protected function item_count(): int {
|
||||
return count($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a boostnavbaritem from the boost navbar.
|
||||
*
|
||||
* @param string|int $itemkey An identifier for the boostnavbaritem
|
||||
*/
|
||||
protected function remove($itemkey): void {
|
||||
|
||||
$itemfound = false;
|
||||
foreach ($this->items as $key => $item) {
|
||||
if ($item->key === $itemkey) {
|
||||
unset($this->items[$key]);
|
||||
$itemfound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$itemfound) {
|
||||
return;
|
||||
}
|
||||
|
||||
$itemcount = $this->item_count();
|
||||
if ($itemcount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->items = array_values($this->items);
|
||||
// Set the last item to last item if it is not.
|
||||
$lastitem = $this->items[$itemcount - 1];
|
||||
if (!$lastitem->is_last()) {
|
||||
$lastitem->set_last(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the action from the last item of the boostnavbaritem.
|
||||
*/
|
||||
protected function remove_last_item_action(): void {
|
||||
$item = end($this->items);
|
||||
$item->action = null;
|
||||
reset($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the second last navbar item. This is for use in the mobile view where we are showing just the second
|
||||
* last item in the breadcrumb navbar.
|
||||
*
|
||||
* @return breakcrumb_navigation_node|null The second last navigation node.
|
||||
*/
|
||||
public function get_penultimate_item(): ?\breadcrumb_navigation_node {
|
||||
$number = $this->item_count() - 2;
|
||||
return ($number >= 0) ? $this->items[$number] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove items that are categories or have no actions associated with them.
|
||||
*/
|
||||
protected function remove_no_link_items(): void {
|
||||
foreach ($this->items as $key => $value) {
|
||||
if (!$value->has_action() || $value->type == \navigation_node::TYPE_SECTION) {
|
||||
unset($this->items[$key]);
|
||||
}
|
||||
}
|
||||
$this->items = array_values($this->items);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace theme_boost\output;
|
||||
|
||||
use moodle_url;
|
||||
use get_string;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
/**
|
||||
* The context header for the boost theme.
|
||||
*
|
||||
* @package theme_boost
|
||||
* @copyright 2021 Adrian Greeve <adrian@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class context_header implements \renderable, \templatable {
|
||||
|
||||
/** \moodle_page This current page information. */
|
||||
protected $page;
|
||||
|
||||
/** array Header data. */
|
||||
protected $headerinfo;
|
||||
|
||||
/** int What level the header should be i.e. 1 = <h1>. */
|
||||
protected $headinglevel;
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param \moodle_page $page This current page information.
|
||||
* @param array $headerinfo Header data.
|
||||
* @param int $headinglevel What level the header should be i.e. 1 = <h1>.
|
||||
*/
|
||||
public function __construct(\moodle_page $page, array $headerinfo = null, int $headinglevel = 1) {
|
||||
$this->page = $page;
|
||||
$this->headerinfo = $headerinfo;
|
||||
$this->headinglevel = $headinglevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an array element for a formatted image.
|
||||
*
|
||||
* @param array $buttons Buttons to format.
|
||||
* @return array The formatted buttons.
|
||||
*/
|
||||
protected function format_button_images(array $buttons): array {
|
||||
foreach ($buttons as $key => $button) {
|
||||
// If no image is provided then just use the title.
|
||||
if (!isset($button['image'])) {
|
||||
$buttons[$key]['formattedimage'] = $button['title'];
|
||||
} else {
|
||||
// Check to see if this is an internal Moodle icon.
|
||||
$internalimage = $this->page->theme->resolve_image_location('t/' . $button['image'], 'moodle');
|
||||
if ($internalimage) {
|
||||
$buttons[$key]['formattedimage'] = 't/' . $button['image'];
|
||||
} else {
|
||||
// Treat as an external image.
|
||||
$buttons[$key]['formattedimage'] = $button['image'];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($button['linkattributes']['class'])) {
|
||||
$class = $button['linkattributes']['class'] . ' btn';
|
||||
} else {
|
||||
$class = 'btn';
|
||||
}
|
||||
// Add the bootstrap 'btn' class for formatting.
|
||||
$buttons[$key]['linkattributes'] = array_merge($button['linkattributes'], ['class' => $class]);
|
||||
$temp = '';
|
||||
foreach ($buttons[$key]['linkattributes'] as $index => $value) {
|
||||
$temp .= "{$index}=\"{$value}\" ";
|
||||
}
|
||||
$buttons[$key]['linkattributes'] = $temp;
|
||||
}
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the logo data.
|
||||
*
|
||||
* @param \renderer_base $output Renderer output data.
|
||||
* @return array Data to display a logo.
|
||||
*/
|
||||
protected function get_logo_data(\renderer_base $output): array {
|
||||
global $SITE;
|
||||
if (!$output->should_display_main_logo($this->headinglevel)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sitename = format_string($SITE->fullname, true, ['context' => \context_course::instance(SITEID)]);
|
||||
if (!isset($heading)) {
|
||||
$heading = $output->heading($this->page->heading, $this->headinglevel, 'sr-only');
|
||||
} else {
|
||||
$heading = $output->heading($heading, $this->headinglevel, 'sr-only');
|
||||
}
|
||||
|
||||
return [
|
||||
'heading' => $heading,
|
||||
'image' => [
|
||||
'src' => $output->get_logo_url(null, 150),
|
||||
'alt' => get_string('logoof', '', $sitename)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Organises all of the relevant data to display the context header.
|
||||
*
|
||||
* @param \renderer_base $output Renderer output data.
|
||||
* @return array Data to display the context header.
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output): array {
|
||||
global $DB, $USER, $CFG;
|
||||
|
||||
$page = $this->page;
|
||||
$context = $page->context;
|
||||
$heading = null;
|
||||
$imagedata = null;
|
||||
$userbuttons = null;
|
||||
|
||||
// Make sure to use the heading if it has been set.
|
||||
if (isset($this->headerinfo['heading'])) {
|
||||
$heading = $this->headerinfo['heading'];
|
||||
} else {
|
||||
$heading = $page->heading;
|
||||
}
|
||||
|
||||
// The user context currently has images and buttons. Other contexts may follow.
|
||||
if (isset($this->headerinfo['user']) || $context->contextlevel == CONTEXT_USER) {
|
||||
if (isset($this->headerinfo['user'])) {
|
||||
$user = $this->headerinfo['user'];
|
||||
} else {
|
||||
// Look up the user information if it is not supplied.
|
||||
$user = $DB->get_record('user', ['id' => $context->instanceid]);
|
||||
}
|
||||
|
||||
// If the user context is set, then use that for capability checks.
|
||||
if (isset($this->headerinfo['usercontext'])) {
|
||||
$context = $this->headerinfo['usercontext'];
|
||||
}
|
||||
|
||||
// Only provide user information if the user is the current user, or a user which the current user can view.
|
||||
// When checking user_can_view_profile(), either:
|
||||
// If the page context is course, check the course context (from the page object) or;
|
||||
// If page context is NOT course, then check across all courses.
|
||||
$course = ($context->contextlevel == CONTEXT_COURSE) ? $page->course : null;
|
||||
|
||||
if (user_can_view_profile($user, $course)) {
|
||||
// Use the user's full name if the heading isn't set.
|
||||
if (empty($heading)) {
|
||||
$heading = fullname($user);
|
||||
}
|
||||
|
||||
$imagedata = $output->user_picture($user, ['size' => 100]);
|
||||
|
||||
// Check to see if we should be displaying a message button.
|
||||
if (!empty($CFG->messaging) && has_capability('moodle/site:sendmessage', $context)) {
|
||||
$userbuttons[] = [
|
||||
'buttontype' => 'message',
|
||||
'title' => get_string('message', 'message'),
|
||||
'url' => new moodle_url('/message/index.php', ['id' => $user->id]),
|
||||
'image' => 'message',
|
||||
'linkattributes' => \core_message\helper::messageuser_link_params($user->id),
|
||||
'page' => (isset($page))
|
||||
];
|
||||
\core_message\helper::togglecontact_requirejs();
|
||||
|
||||
if ($USER->id != $user->id) {
|
||||
$iscontact = \core_message\api::is_contact($USER->id, $user->id);
|
||||
$contacttitle = $iscontact ? 'removefromyourcontacts' : 'addtoyourcontacts';
|
||||
$contacturlaction = $iscontact ? 'removecontact' : 'addcontact';
|
||||
$contactimage = $iscontact ? 'removecontact' : 'addcontact';
|
||||
$userbuttons[] = [
|
||||
'buttontype' => 'togglecontact',
|
||||
'title' => get_string($contacttitle, 'message'),
|
||||
'url' => new moodle_url('/message/index.php', [
|
||||
'user1' => $USER->id,
|
||||
'user2' => $user->id,
|
||||
$contacturlaction => $user->id,
|
||||
'sesskey' => sesskey()
|
||||
]),
|
||||
'image' => $contactimage,
|
||||
'linkattributes' => \core_message\helper::togglecontact_link_params($user, $iscontact),
|
||||
'page' => (isset($page))
|
||||
];
|
||||
\core_message\helper::messageuser_requirejs();
|
||||
}
|
||||
|
||||
$page->requires->string_for_js('changesmadereallygoaway', 'moodle');
|
||||
$userbuttons = $this->format_button_images($userbuttons);
|
||||
}
|
||||
} else {
|
||||
$heading = null;
|
||||
}
|
||||
}
|
||||
|
||||
$prefix = null;
|
||||
if ($context->contextlevel == CONTEXT_MODULE) {
|
||||
$heading = $page->cm->name;
|
||||
$imagedata = $output->pix_icon('icon', '', $page->activityname);
|
||||
$prefix = get_string('modulename', $page->activityname);
|
||||
}
|
||||
|
||||
$logo = $this->get_logo_data($output);
|
||||
if (empty($logo)) {
|
||||
$logo = '';
|
||||
$headingdisplay = $this->headinglevel + 1;
|
||||
$headingdisplay = 'h' . $headingdisplay;
|
||||
if (!isset($heading)) {
|
||||
$heading = $output->heading($page->heading, $this->headinglevel, $headingdisplay);
|
||||
} else {
|
||||
$heading = $output->heading($heading, $this->headinglevel, $headingdisplay);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'logo' => $logo,
|
||||
'heading' => $heading,
|
||||
'imagedata' => $imagedata,
|
||||
'userbuttons' => $userbuttons,
|
||||
'prefix' => $prefix
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
namespace theme_boost\output;
|
||||
|
||||
use moodle_url;
|
||||
use html_writer;
|
||||
use get_string;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
@@ -42,4 +44,204 @@ class core_renderer extends \core_renderer {
|
||||
return $this->render_single_button($button);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the "breadcrumb" for all pages in boost.
|
||||
*
|
||||
* @return string the HTML for the navbar.
|
||||
*/
|
||||
public function navbar(): string {
|
||||
$newnav = new \theme_boost\boostnavbar($this->page->navbar);
|
||||
return $this->render_from_template('core/navbar', $newnav);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the context header for the page.
|
||||
*
|
||||
* @param array $headerinfo Heading information.
|
||||
* @param int $headinglevel What 'h' level to make the heading.
|
||||
* @return string A rendered context header.
|
||||
*/
|
||||
public function context_header($headerinfo = null, $headinglevel = 1): string {
|
||||
global $DB, $USER, $CFG, $SITE;
|
||||
require_once($CFG->dirroot . '/user/lib.php');
|
||||
$context = $this->page->context;
|
||||
$heading = null;
|
||||
$imagedata = null;
|
||||
$subheader = null;
|
||||
$userbuttons = null;
|
||||
|
||||
// Make sure to use the heading if it has been set.
|
||||
if (isset($headerinfo['heading'])) {
|
||||
$heading = $headerinfo['heading'];
|
||||
} else {
|
||||
$heading = $this->page->heading;
|
||||
}
|
||||
|
||||
// The user context currently has images and buttons. Other contexts may follow.
|
||||
if (isset($headerinfo['user']) || $context->contextlevel == CONTEXT_USER) {
|
||||
if (isset($headerinfo['user'])) {
|
||||
$user = $headerinfo['user'];
|
||||
} else {
|
||||
// Look up the user information if it is not supplied.
|
||||
$user = $DB->get_record('user', array('id' => $context->instanceid));
|
||||
}
|
||||
|
||||
// If the user context is set, then use that for capability checks.
|
||||
if (isset($headerinfo['usercontext'])) {
|
||||
$context = $headerinfo['usercontext'];
|
||||
}
|
||||
|
||||
// Only provide user information if the user is the current user, or a user which the current user can view.
|
||||
// When checking user_can_view_profile(), either:
|
||||
// If the page context is course, check the course context (from the page object) or;
|
||||
// If page context is NOT course, then check across all courses.
|
||||
$course = ($this->page->context->contextlevel == CONTEXT_COURSE) ? $this->page->course : null;
|
||||
|
||||
if (user_can_view_profile($user, $course)) {
|
||||
// Use the user's full name if the heading isn't set.
|
||||
if (empty($heading)) {
|
||||
$heading = fullname($user);
|
||||
}
|
||||
|
||||
$imagedata = $this->user_picture($user, array('size' => 100));
|
||||
|
||||
// Check to see if we should be displaying a message button.
|
||||
if (!empty($CFG->messaging) && has_capability('moodle/site:sendmessage', $context)) {
|
||||
$userbuttons = array(
|
||||
'messages' => array(
|
||||
'buttontype' => 'message',
|
||||
'title' => get_string('message', 'message'),
|
||||
'url' => new moodle_url('/message/index.php', array('id' => $user->id)),
|
||||
'image' => 'message',
|
||||
'linkattributes' => \core_message\helper::messageuser_link_params($user->id),
|
||||
'page' => $this->page
|
||||
)
|
||||
);
|
||||
|
||||
if ($USER->id != $user->id) {
|
||||
$iscontact = \core_message\api::is_contact($USER->id, $user->id);
|
||||
$contacttitle = $iscontact ? 'removefromyourcontacts' : 'addtoyourcontacts';
|
||||
$contacturlaction = $iscontact ? 'removecontact' : 'addcontact';
|
||||
$contactimage = $iscontact ? 'removecontact' : 'addcontact';
|
||||
$userbuttons['togglecontact'] = array(
|
||||
'buttontype' => 'togglecontact',
|
||||
'title' => get_string($contacttitle, 'message'),
|
||||
'url' => new moodle_url('/message/index.php', array(
|
||||
'user1' => $USER->id,
|
||||
'user2' => $user->id,
|
||||
$contacturlaction => $user->id,
|
||||
'sesskey' => sesskey())
|
||||
),
|
||||
'image' => $contactimage,
|
||||
'linkattributes' => \core_message\helper::togglecontact_link_params($user, $iscontact),
|
||||
'page' => $this->page
|
||||
);
|
||||
}
|
||||
|
||||
$this->page->requires->string_for_js('changesmadereallygoaway', 'moodle');
|
||||
}
|
||||
} else {
|
||||
$heading = null;
|
||||
}
|
||||
}
|
||||
|
||||
$prefix = null;
|
||||
if ($context->contextlevel == CONTEXT_MODULE) {
|
||||
$heading = $this->page->cm->name;
|
||||
$imagedata = $this->pix_icon('icon', '', $this->page->activityname);
|
||||
$prefix = get_string('modulename', $this->page->activityname);
|
||||
}
|
||||
|
||||
if ($this->should_display_main_logo($headinglevel)) {
|
||||
$sitename = format_string($SITE->fullname, true, ['context' => \context_course::instance(SITEID)]);
|
||||
// Logo.
|
||||
$html = html_writer::div(
|
||||
html_writer::empty_tag('img', [
|
||||
'src' => $this->get_logo_url(null, 150),
|
||||
'alt' => get_string('logoof', '', $sitename),
|
||||
'class' => 'img-fluid'
|
||||
]),
|
||||
'logo'
|
||||
);
|
||||
// Heading.
|
||||
if (!isset($heading)) {
|
||||
$html .= $this->heading($this->page->heading, $headinglevel, 'sr-only');
|
||||
} else {
|
||||
$html .= $this->heading($heading, $headinglevel, 'sr-only');
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
$contextheader = new \context_header($heading, $headinglevel, $imagedata, $userbuttons, $prefix);
|
||||
return $this->render_context_header($contextheader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the header bar.
|
||||
*
|
||||
* @param context_header $contextheader Header bar object.
|
||||
* @return string HTML for the header bar.
|
||||
*/
|
||||
protected function render_context_header(\context_header $contextheader) {
|
||||
|
||||
// Generate the heading first and before everything else as we might have to do an early return.
|
||||
if (!isset($contextheader->heading)) {
|
||||
$heading = $this->heading($this->page->heading, $contextheader->headinglevel, 'h2');
|
||||
} else {
|
||||
$heading = $this->heading($contextheader->heading, $contextheader->headinglevel, 'h2');
|
||||
}
|
||||
|
||||
$showheader = empty($this->page->layout_options['nocontextheader']);
|
||||
if (!$showheader) {
|
||||
// Return the heading wrapped in an sr-only element so it is only visible to screen-readers.
|
||||
return html_writer::div($heading, 'sr-only');
|
||||
}
|
||||
|
||||
// All the html stuff goes here.
|
||||
$html = html_writer::start_div('page-context-header');
|
||||
|
||||
// Image data.
|
||||
if (isset($contextheader->imagedata)) {
|
||||
// Header specific image.
|
||||
$html .= html_writer::div($contextheader->imagedata, 'page-header-image icon-size-6');
|
||||
}
|
||||
|
||||
// Headings.
|
||||
if (isset($contextheader->prefix)) {
|
||||
$prefix = html_writer::div($contextheader->prefix, 'text-muted text-uppercase');
|
||||
$heading = $prefix . $heading;
|
||||
}
|
||||
$html .= html_writer::tag('div', $heading, array('class' => 'page-header-headings'));
|
||||
|
||||
// Buttons.
|
||||
if (isset($contextheader->additionalbuttons)) {
|
||||
$html .= html_writer::start_div('btn-group header-button-group');
|
||||
foreach ($contextheader->additionalbuttons as $button) {
|
||||
if (!isset($button->page)) {
|
||||
// Include js for messaging.
|
||||
if ($button['buttontype'] === 'togglecontact') {
|
||||
\core_message\helper::togglecontact_requirejs();
|
||||
}
|
||||
if ($button['buttontype'] === 'message') {
|
||||
\core_message\helper::messageuser_requirejs();
|
||||
}
|
||||
$image = $this->pix_icon($button['formattedimage'], $button['title'], 'moodle', array(
|
||||
'class' => 'iconsmall',
|
||||
'role' => 'presentation'
|
||||
));
|
||||
$image .= html_writer::span($button['title'], 'header-button-title');
|
||||
} else {
|
||||
$image = html_writer::empty_tag('img', array(
|
||||
'src' => $button['formattedimage'],
|
||||
'role' => 'presentation'
|
||||
));
|
||||
}
|
||||
$html .= html_writer::link($button['url'], html_writer::tag('span', $image), $button['linkattributes']);
|
||||
}
|
||||
$html .= html_writer::end_div();
|
||||
}
|
||||
$html .= html_writer::end_div();
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,20 @@ $hasblocks = strpos($blockshtml, 'data-block=') !== false;
|
||||
$buildregionmainsettings = !$PAGE->include_region_main_settings_in_header_actions();
|
||||
// If the settings menu will be included in the header then don't add it here.
|
||||
$regionmainsettingsmenu = $buildregionmainsettings ? $OUTPUT->region_main_settings_menu() : false;
|
||||
|
||||
$secondarynavigation = false;
|
||||
if (!defined('BEHAT_SITE_RUNNING')) {
|
||||
$buildsecondarynavigation = $PAGE->has_secondary_navigation();
|
||||
if ($buildsecondarynavigation) {
|
||||
$moremenu = new \core\navigation\output\more_menu($PAGE->secondarynav, 'nav-tabs');
|
||||
$secondarynavigation = $moremenu->export_for_template($OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
$primary = new core\navigation\output\primary($PAGE);
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$primarymenu = $primary->export_for_template($renderer);
|
||||
|
||||
$templatecontext = [
|
||||
'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]),
|
||||
'output' => $OUTPUT,
|
||||
@@ -50,11 +64,13 @@ $templatecontext = [
|
||||
'bodyattributes' => $bodyattributes,
|
||||
'navdraweropen' => $navdraweropen,
|
||||
'regionmainsettingsmenu' => $regionmainsettingsmenu,
|
||||
'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu)
|
||||
'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu),
|
||||
'primarymoremenu' => $primarymenu['moremenu'],
|
||||
'secondarymoremenu' => $secondarynavigation,
|
||||
'usermenu' => $primarymenu['user'],
|
||||
'langmenu' => $primarymenu['lang'],
|
||||
];
|
||||
|
||||
$nav = $PAGE->flatnav;
|
||||
$templatecontext['flatnavigation'] = $nav;
|
||||
$templatecontext['firstcollectionlabel'] = $nav->get_collectionlabel();
|
||||
echo $OUTPUT->render_from_template('theme_boost/columns2', $templatecontext);
|
||||
|
||||
|
||||
@@ -69,6 +69,19 @@ $buildregionmainsettings = !$PAGE->include_region_main_settings_in_header_action
|
||||
// If the settings menu will be included in the header then don't add it here.
|
||||
$regionmainsettingsmenu = $buildregionmainsettings ? $OUTPUT->region_main_settings_menu() : false;
|
||||
|
||||
$secondarynavigation = false;
|
||||
if (!defined('BEHAT_SITE_RUNNING')) {
|
||||
$buildsecondarynavigation = $PAGE->has_secondary_navigation();
|
||||
if ($buildsecondarynavigation) {
|
||||
$moremenu = new \core\navigation\output\more_menu($PAGE->secondarynav, 'nav-tabs');
|
||||
$secondarynavigation = $moremenu->export_for_template($OUTPUT);
|
||||
}
|
||||
}
|
||||
|
||||
$primary = new core\navigation\output\primary($PAGE);
|
||||
$renderer = $PAGE->get_renderer('core');
|
||||
$primarymenu = $primary->export_for_template($renderer);
|
||||
|
||||
$templatecontext = [
|
||||
'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]),
|
||||
'output' => $OUTPUT,
|
||||
@@ -80,7 +93,11 @@ $templatecontext = [
|
||||
'blockdraweropen' => $blockdraweropen,
|
||||
'regionmainsettingsmenu' => $regionmainsettingsmenu,
|
||||
'courseindex' => $courseindex,
|
||||
'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu)
|
||||
'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu),
|
||||
'primarymoremenu' => $primarymenu['moremenu'],
|
||||
'secondarymoremenu' => $secondarynavigation,
|
||||
'usermenu' => $primarymenu['user'],
|
||||
'langmenu' => $primarymenu['lang'],
|
||||
];
|
||||
|
||||
$nav = $PAGE->flatnav;
|
||||
|
||||
@@ -45,3 +45,6 @@ $breadcrumb-divider-rtl: "◀" !default;
|
||||
@import "moodle/navbar";
|
||||
@import "moodle/reportbuilder";
|
||||
@import "moodle/courseindex";
|
||||
@import "moodle/moremenu";
|
||||
@import "moodle/primarynavigation";
|
||||
@import "moodle/secondarynavigation";
|
||||
|
||||
@@ -379,10 +379,12 @@ img.resize {
|
||||
|
||||
.action-menu .dropdown-toggle {
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.action-menu {
|
||||
white-space: nowrap;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.block img.resize {
|
||||
@@ -2076,7 +2078,8 @@ nav.navbar .logo img {
|
||||
|
||||
.page-header-image {
|
||||
margin-right: 1em;
|
||||
margin-bottom: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
margin-top: 0.7em;
|
||||
& > a {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -2706,7 +2709,7 @@ $picker-emojis-per-row: 7 !default;
|
||||
}
|
||||
}
|
||||
}
|
||||
.nav-tabs,
|
||||
.nav-tabs:not(.more-nav),
|
||||
.nav-pills {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
.moremenu {
|
||||
opacity: 0;
|
||||
height: $moremenu-height;
|
||||
&.observed {
|
||||
opacity: 1;
|
||||
}
|
||||
.nav-link {
|
||||
height: $moremenu-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.nav-tabs {
|
||||
margin-left: 0;
|
||||
}
|
||||
// Styling for dropdown menus inside the MoreButton.
|
||||
.dropdownmoremenu > .dropdown-menu {
|
||||
& > .dropdown-item {
|
||||
padding: 0;
|
||||
}
|
||||
.dropdown-menu {
|
||||
position: static;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
&.show {
|
||||
display: block;
|
||||
}
|
||||
.dropdown-item {
|
||||
background-color: $gray-100;
|
||||
@include hover-focus() {
|
||||
color: $dropdown-link-hover-color;
|
||||
@include gradient-bg($dropdown-link-active-bg);
|
||||
}
|
||||
}
|
||||
.dropdown-divider {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,57 @@
|
||||
.dropdown-toggle::after {
|
||||
display: none;
|
||||
}
|
||||
.dropdown-menu {
|
||||
min-width: 235px;
|
||||
.carousel-navigation-link {
|
||||
> * {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.dropdown-item {
|
||||
padding: .25rem 1.75rem .25rem .75rem;
|
||||
&.carousel-navigation-link::after {
|
||||
font-family: FontAwesome;
|
||||
content: $fa-var-caret-right;
|
||||
font-size: 1rem;
|
||||
right: .75rem;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
.submenu {
|
||||
.header {
|
||||
padding: .25rem .75rem;
|
||||
font-size: .975rem;
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
.items {
|
||||
.dropdown-item {
|
||||
&[aria-current="true"]::before {
|
||||
font-family: FontAwesome;
|
||||
content: $fa-var-check;
|
||||
font-size: 0.75rem;
|
||||
padding-left: .25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.langmenu {
|
||||
.dropdown-menu {
|
||||
.dropdown-item {
|
||||
&[aria-current="true"]::before {
|
||||
content: "\f00c";
|
||||
font-size: 0.75rem;
|
||||
padding-left: .25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.moodle-actionmenu .menubar,
|
||||
.action-menu-trigger .dropdown {
|
||||
@@ -40,6 +91,33 @@
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
.dir-rtl .navbar.fixed-top {
|
||||
.usermenu {
|
||||
.dropdown-menu {
|
||||
.dropdown-item {
|
||||
&.carousel-navigation-link::after {
|
||||
content: $fa-var-caret-left;
|
||||
}
|
||||
}
|
||||
.carousel {
|
||||
.carousel-inner {
|
||||
.carousel-item-prev.carousel-item-right,
|
||||
.carousel-item-next.carousel-item-left {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.carousel-item-next,
|
||||
.carousel-item-right.active {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
.carousel-item-prev,
|
||||
.carousel-item-left.active {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#page {
|
||||
margin-top: $navbar-height;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.navbar.fixed-top {
|
||||
.moremenu {
|
||||
height: $navbar-height;
|
||||
.nav-link {
|
||||
height: $navbar-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user