Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d41ac46f4 | |||
| afa68b9685 | |||
| f5c82455b8 | |||
| 684a375b45 | |||
| dc84fcfab0 | |||
| 093aedf798 | |||
| c1aacb3e28 | |||
| c5b1604e81 | |||
| 647b9dc064 | |||
| 3048a162cb | |||
| b48193c056 | |||
| 3e38c84315 | |||
| 5bf91592cd | |||
| b3b1d731ab | |||
| 0caedaab7c | |||
| 4461d3f6d6 | |||
| 742bc470ab | |||
| be10cd772b | |||
| c1aae7d366 | |||
| 19ea4c183d | |||
| e5348292f2 | |||
| 1d186d7f36 | |||
| 7ecbcb3c33 | |||
| a645dc7051 | |||
| 00d0724b66 | |||
| 108fc684de | |||
| e1b3e14886 | |||
| 80e8737375 | |||
| 95b5ca87eb | |||
| ab0792d943 | |||
| 022a3d71fd | |||
| 9dbd03e51d | |||
| 93b53c3bb1 | |||
| c92cfe9aed | |||
| 1fc3e502f0 | |||
| b49bb7a6b5 | |||
| cb49940156 | |||
| 981272bc2c | |||
| 3cdf09eb8b | |||
| 1f3550847b | |||
| 3fdb0f3580 | |||
| 4ff803a9f3 | |||
| 43386365b8 | |||
| b034a4a24a | |||
| f0ba09aabe | |||
| 13aa29cbae | |||
| d78d270031 | |||
| c852ec91ab | |||
| 1f4489ed59 | |||
| 479da404db | |||
| c862fc046c | |||
| 7b172f51b9 | |||
| 2936dbdd3f | |||
| 3d847c154a | |||
| 1b0d03b87e | |||
| d5002415ce | |||
| 5eef98ee05 | |||
| f4220e56eb | |||
| dda25ae3c8 | |||
| e97accec36 | |||
| d5814b74be | |||
| ea1bc14949 | |||
| 0015c873ce | |||
| a95b757384 | |||
| 2d1c685828 | |||
| 25e4619ae7 | |||
| 341ec5ba81 |
@@ -432,13 +432,13 @@ class api {
|
||||
public static function get_qrlogin_key(stdClass $mobilesettings) {
|
||||
global $USER;
|
||||
// Delete previous keys.
|
||||
delete_user_key('tool_mobile', $USER->id);
|
||||
delete_user_key('tool_mobile/qrlogin', $USER->id);
|
||||
|
||||
// Create a new key.
|
||||
$iprestriction = !empty($mobilesettings->qrsameipcheck) ? getremoteaddr(null) : null;
|
||||
$qrkeyttl = !empty($mobilesettings->qrkeyttl) ? $mobilesettings->qrkeyttl : self::LOGIN_QR_KEY_TTL;
|
||||
$validuntil = time() + $qrkeyttl;
|
||||
return create_user_key('tool_mobile', $USER->id, null, $iprestriction, $validuntil);
|
||||
return create_user_key('tool_mobile/qrlogin', $USER->id, null, $iprestriction, $validuntil);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -648,8 +648,8 @@ class external extends external_api {
|
||||
api::check_autologin_prerequisites($params['userid']); // Checks https, avoid site admins using this...
|
||||
|
||||
// Validate and delete the key.
|
||||
$key = validate_user_key($params['qrloginkey'], 'tool_mobile', null);
|
||||
delete_user_key('tool_mobile', $params['userid']);
|
||||
$key = validate_user_key($params['qrloginkey'], 'tool_mobile/qrlogin', null);
|
||||
delete_user_key('tool_mobile/qrlogin', $params['userid']);
|
||||
|
||||
// Double check key belong to user.
|
||||
if ($key->userid != $params['userid']) {
|
||||
|
||||
@@ -66,7 +66,7 @@ class provider implements
|
||||
FROM {user_private_key} k
|
||||
JOIN {user} u ON k.userid = u.id
|
||||
JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel
|
||||
WHERE k.userid = :userid AND k.script = 'tool_mobile'";
|
||||
WHERE k.userid = :userid AND (k.script = 'tool_mobile' OR k.script = 'tool_mobile/qrlogin')";
|
||||
$params = ['userid' => $userid, 'contextlevel' => CONTEXT_USER];
|
||||
$contextlist = new contextlist();
|
||||
$contextlist->add_from_sql($sql, $params);
|
||||
@@ -88,6 +88,7 @@ class provider implements
|
||||
|
||||
// Add users based on userkey.
|
||||
\core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile');
|
||||
\core_userkey\privacy\provider::get_user_contexts_with_script($userlist, $context, 'tool_mobile/qrlogin');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,6 +109,7 @@ class provider implements
|
||||
}
|
||||
// Export associated userkeys.
|
||||
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile');
|
||||
\core_userkey\privacy\provider::export_userkeys($context, [], 'tool_mobile/qrlogin');
|
||||
}
|
||||
/**
|
||||
* Export all user preferences for the plugin.
|
||||
@@ -138,6 +140,7 @@ class provider implements
|
||||
$userid = $context->instanceid;
|
||||
// Delete all the userkeys.
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
|
||||
}
|
||||
/**
|
||||
* Delete all user data for the specified user, in the specified contexts.
|
||||
@@ -158,6 +161,7 @@ class provider implements
|
||||
$userid = $context->instanceid;
|
||||
// Delete all the userkeys.
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,5 +182,6 @@ class provider implements
|
||||
|
||||
// Delete all the userkeys.
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile', $userid);
|
||||
\core_userkey\privacy\provider::delete_userkeys('tool_mobile/qrlogin', $userid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,14 @@ if (empty($service)) {
|
||||
throw new moodle_exception('servicenotavailable', 'webservice');
|
||||
}
|
||||
|
||||
// If the user is using the inapp (embedded) browser, we need to set the Secure and Partitioned attributes to the session cookie.
|
||||
if (\core_useragent::is_moodle_app()) {
|
||||
\core\session\utility\cookie_helper::add_attributes_to_cookie_response_header(
|
||||
cookiename: "MoodleSession{$CFG->sessioncookie}",
|
||||
attributes: ['Secure', 'Partitioned'],
|
||||
);
|
||||
}
|
||||
|
||||
require_login(0, false);
|
||||
|
||||
// Require an active user: not guest, not suspended.
|
||||
|
||||
@@ -87,7 +87,8 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$context1 = \context_user::instance($user1->id);
|
||||
$context2 = \context_user::instance($user2->id);
|
||||
$key1 = get_user_key('tool_mobile', $user1->id);
|
||||
$key2 = get_user_key('tool_mobile', $user2->id);
|
||||
$key2 = get_user_key('tool_mobile/qrlogin', $user1->id);
|
||||
$key3 = get_user_key('tool_mobile', $user2->id);
|
||||
|
||||
// Ensure only user1 is found in context1.
|
||||
$userlist = new \core_privacy\local\request\userlist($context1, $component);
|
||||
@@ -175,12 +176,15 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
$context1 = \context_user::instance($user1->id);
|
||||
$context2 = \context_user::instance($user2->id);
|
||||
$keyvalue1 = get_user_key('tool_mobile', $user1->id);
|
||||
$keyvalue2 = get_user_key('tool_mobile', $user2->id);
|
||||
$keyvalue2 = get_user_key('tool_mobile/qrlogin', $user1->id);
|
||||
$keyvalue3 = get_user_key('tool_mobile', $user2->id);
|
||||
$key1 = $DB->get_record('user_private_key', ['value' => $keyvalue1]);
|
||||
|
||||
// Before deletion, we should have 2 user_private_keys.
|
||||
// Before deletion, we should have 2 user_private_keys for tool_mobile and one for tool_mobile/qrlogin.
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
|
||||
$this->assertEquals(2, $count);
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']);
|
||||
$this->assertEquals(1, $count);
|
||||
|
||||
// Ensure deleting wrong user in the user context does nothing.
|
||||
$approveduserids = [$user2->id];
|
||||
@@ -198,6 +202,8 @@ class provider_test extends \core_privacy\tests\provider_testcase {
|
||||
// Ensure only user1's data is deleted, user2's remains.
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile']);
|
||||
$this->assertEquals(1, $count);
|
||||
$count = $DB->count_records('user_private_key', ['script' => 'tool_mobile/qrlogin']);
|
||||
$this->assertEquals(0, $count);
|
||||
|
||||
$params = ['script' => $component];
|
||||
$userid = $DB->get_field_select('user_private_key', 'userid', 'script = :script', $params);
|
||||
|
||||
@@ -63,7 +63,7 @@ if ($cancel) {
|
||||
}
|
||||
redirect($url);
|
||||
} else if ($continue) {
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
// Handle backups.
|
||||
if (strtolower($importinfo->get_resource()->get_extension()) == 'mbz') {
|
||||
|
||||
@@ -81,7 +81,7 @@ switch ($config->type) {
|
||||
}
|
||||
|
||||
if ($import && $module) {
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
$handlerinfo = $handlerregistry->get_resource_handler_for_mod_and_strategy($importinfo->get_resource(), $module, $strategy);
|
||||
if (is_null($handlerinfo)) {
|
||||
|
||||
@@ -58,7 +58,7 @@ class helper {
|
||||
FROM {block_recentlyaccesseditems} rai
|
||||
JOIN {course} c ON c.id = rai.courseid
|
||||
WHERE userid = :userid
|
||||
ORDER BY rai.timeaccess DESC";
|
||||
ORDER BY rai.timeaccess DESC, rai.id DESC";
|
||||
$records = $DB->get_records_sql($sql, $paramsql);
|
||||
$order = 0;
|
||||
|
||||
|
||||
@@ -3579,6 +3579,18 @@ function calendar_get_view(\calendar_information $calendar, $view, $includenavig
|
||||
}
|
||||
}
|
||||
|
||||
// Check if $data has events.
|
||||
if (isset($data->events)) {
|
||||
// Let's check and sanitize all "name" in $data->events before it's sent to front end.
|
||||
foreach ($data->events as $d) {
|
||||
$name = $d->name ?? null;
|
||||
// Encode special characters if our decoded name does not match the original name.
|
||||
if ($name && (html_entity_decode($name) !== $name)) {
|
||||
$d->name = htmlspecialchars(html_entity_decode($name), ENT_QUOTES, 'utf-8');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$data, $template];
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ $filename = str_replace('/', '', str_replace(' ', '_', $courseinfo->shortname))
|
||||
|
||||
// If download confirmed, prepare and start the zipstream of the course download content.
|
||||
if ($isdownload) {
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
|
||||
$exportoptions = null;
|
||||
|
||||
|
||||
@@ -9,11 +9,13 @@ Feature: Display and choose from the available activities in course
|
||||
| username | firstname | lastname | email |
|
||||
| teacher | Teacher | 1 | teacher@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | format |
|
||||
| Course | C | topics |
|
||||
| fullname | shortname | format | startdate |
|
||||
| Course | C | topics | |
|
||||
| Course 2 | C2 | weeks | 95713920 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher | C | editingteacher |
|
||||
| user | course | role |
|
||||
| teacher | C | editingteacher |
|
||||
| teacher | C2 | editingteacher |
|
||||
And the following config values are set as admin:
|
||||
| enablemoodlenet | 0 | tool_moodlenet |
|
||||
And I log in as "teacher"
|
||||
@@ -44,6 +46,12 @@ Feature: Display and choose from the available activities in course
|
||||
When I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
|
||||
Then I should see "Assignment" in the "help" "core_course > Activity chooser screen"
|
||||
And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback."
|
||||
# Confirm show summary also works for weekly format course
|
||||
And I am on "C2" course homepage with editing mode on
|
||||
And I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section"
|
||||
And I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
|
||||
And I should see "Assignment" in the "help" "core_course > Activity chooser screen"
|
||||
And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback."
|
||||
|
||||
Scenario: Hide summary
|
||||
Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
|
||||
@@ -55,6 +63,15 @@ Feature: Display and choose from the available activities in course
|
||||
And "help" "core_course > Activity chooser screen" should not be visible
|
||||
And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen"
|
||||
And I should not see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "Add an activity or resource" "dialogue"
|
||||
# Confirm hide summary also works for weekly format course
|
||||
And I am on "C2" course homepage with editing mode on
|
||||
And I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section"
|
||||
And I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
|
||||
And I click on "Back" "button" in the "help" "core_course > Activity chooser screen"
|
||||
And "modules" "core_course > Activity chooser screen" should be visible
|
||||
And "help" "core_course > Activity chooser screen" should not be visible
|
||||
And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen"
|
||||
And I should not see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "Add an activity or resource" "dialogue"
|
||||
|
||||
Scenario: View recommended activities
|
||||
When I log out
|
||||
@@ -233,3 +250,69 @@ Feature: Display and choose from the available activities in course
|
||||
And I reload the page
|
||||
And I click on "Add an activity or resource" "button" in the "Topic 1" "section"
|
||||
And "Recommended" "link" should appear before "Activities" "link" in the "Add an activity or resource" "dialogue"
|
||||
|
||||
Scenario: Teacher can navigate through activity chooser in Topics format course
|
||||
When I click on "Add an activity or resource" "button" in the "Topic 1" "section"
|
||||
Then I should see "All" in the "Add an activity or resource" "dialogue"
|
||||
And I press the tab key
|
||||
And I press the tab key
|
||||
And I press the tab key
|
||||
And I press the tab key
|
||||
# Confirm right key works
|
||||
And I press the right key
|
||||
And I press the right key
|
||||
And the focused element is "Chat" "menuitem" in the "Add an activity or resource" "dialogue"
|
||||
# Confirm left key works
|
||||
And I press the left key
|
||||
And the focused element is "Book" "menuitem" in the "Add an activity or resource" "dialogue"
|
||||
# Confirm clicking "x" button closes modal
|
||||
And I click on "Close" "button" in the "Add an activity or resource" "dialogue"
|
||||
And "Add an activity or resource" "dialogue" should not be visible
|
||||
And I click on "Add an activity or resource" "button" in the "Topic 1" "section"
|
||||
# Confirm escape key closes the modal
|
||||
And I press the escape key
|
||||
And "Add an activity or resource" "dialogue" should not be visible
|
||||
|
||||
Scenario: Teacher can navigate through activity chooser in Weekly format course
|
||||
Given I am on "C2" course homepage with editing mode on
|
||||
When I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section"
|
||||
Then I should see "All" in the "Add an activity or resource" "dialogue"
|
||||
And I press the tab key
|
||||
And I press the tab key
|
||||
And I press the tab key
|
||||
And I press the tab key
|
||||
# Confirm right key works
|
||||
And I press the right key
|
||||
And I press the right key
|
||||
And the focused element is "Chat" "menuitem" in the "Add an activity or resource" "dialogue"
|
||||
# Confirm left key works
|
||||
And I press the left key
|
||||
And the focused element is "Book" "menuitem" in the "Add an activity or resource" "dialogue"
|
||||
# Confirm clicking "x" button closes modal
|
||||
And I click on "Close" "button" in the "Add an activity or resource" "dialogue"
|
||||
And "Add an activity or resource" "dialogue" should not be visible
|
||||
And I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section"
|
||||
# Confirm escape key closes the modal
|
||||
And I press the escape key
|
||||
And "Add an activity or resource" "dialogue" should not be visible
|
||||
|
||||
Scenario: Teacher can access 'More help' from activity information in activity chooser
|
||||
Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
|
||||
When I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
|
||||
# Confirm more help link exists
|
||||
Then "More help" "link" should exist
|
||||
# Confirm that corresponding help icon exist
|
||||
And ".iconhelp" "css_element" should exist
|
||||
# Confirm that link opens in new window
|
||||
And "Opens in new window" "link" should be visible
|
||||
# Confirm the same behaviour for weekly format course
|
||||
And I am on "C2" course homepage with editing mode on
|
||||
And I click on "Add an activity or resource" "button" in the "13 January - 19 January" "section"
|
||||
And I should see "All" in the "Add an activity or resource" "dialogue"
|
||||
And I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
|
||||
# Confirm more help link exists
|
||||
And "More help" "link" should exist
|
||||
# Confirm that corresponding help icon exist
|
||||
And ".iconhelp" "css_element" should exist
|
||||
# Confirm that link opens in new window
|
||||
And "Opens in new window" "link" should be visible
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
@core @core_course @core_tag
|
||||
Feature: Delete activity tags during course reset
|
||||
As an admin,
|
||||
I should be able to delete activity tags by performing course reset
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | course | idnumber |
|
||||
| book | Test Book | C1 | book1 |
|
||||
| forum | Test Forum | C1 | forum1 |
|
||||
| glossary | Test Glossary | C1 | glossary1 |
|
||||
|
||||
@javascript
|
||||
Scenario: Delete book chapter tags using course reset
|
||||
# Added multiple tags to confirm that all tags are deleted on course reset.
|
||||
Given the following "mod_book > chapters" exist:
|
||||
| book | title | content | tags |
|
||||
| Test Book | Chapter 1 | Chapter 1 content | SampleTag, ChapterTag |
|
||||
# Perform course reset without checking anything.
|
||||
And I log in as "admin"
|
||||
And I am on the "Course 1" "reset" page
|
||||
And I press "Reset"
|
||||
And I press "Continue"
|
||||
# Confirm that book chapter tags are not deleted.
|
||||
When I am on the "Test Book" "book activity" page
|
||||
Then I should see "SampleTag"
|
||||
And I should see "ChapterTag"
|
||||
# Delete book chapter tags using course reset.
|
||||
And I am on the "Course 1" "reset" page
|
||||
And I expand all fieldsets
|
||||
And I click on "Remove all book tags" "checkbox"
|
||||
And I press "Reset"
|
||||
# Confirm that book chapter tags are deleted.
|
||||
And I should see "Book tags have been deleted" in the "Books" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Test Book" "book activity" page
|
||||
And I should not see "SampleTag"
|
||||
And I should not see "ChapterTag"
|
||||
|
||||
@javascript
|
||||
Scenario Outline: Delete forum discussion tags using course reset
|
||||
Given the following "mod_forum > discussions" exist:
|
||||
| user | forum | name | message | tags |
|
||||
| admin | forum1 | Discussion 1 | Discussion 1 message | SampleTag, DiscussionTag |
|
||||
# Perform course reset without checking anything.
|
||||
And I am on the "Course 1" "reset" page logged in as admin
|
||||
And I press "Reset"
|
||||
And I press "Continue"
|
||||
# Confirm that forum discussion tags are not deleted.
|
||||
When I am on the "Test Forum" "forum activity" page
|
||||
And I follow "Discussion 1"
|
||||
Then I should see "SampleTag"
|
||||
And I should see "DiscussionTag"
|
||||
And I am on the "Course 1" "reset" page
|
||||
And I expand all fieldsets
|
||||
# Depending on <resetcheck> value, either delete all discussion posts or remove all forum discussion tags only.
|
||||
And I click on "<resetcheck>" "checkbox"
|
||||
# Confirm `Remove all forum tags` is disabled when `Delete all posts` on previous step is checked.
|
||||
And the "Remove all forum tags" "checkbox" should be <canbechecked>
|
||||
And I press "Reset"
|
||||
And I should see "<resetmessage>" in the "Forums" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Test Forum" "forum activity" page
|
||||
# Confirm discussion is deleted when `Delete all posts` was checked.
|
||||
And I <forumview> see "There are no discussion topics yet in this forum"
|
||||
# Confirm all discussion tags are deleted.
|
||||
And I should not see "SampleTag"
|
||||
And I should not see "DiscussionTag"
|
||||
|
||||
Examples:
|
||||
| resetcheck | resetmessage | canbechecked | forumview |
|
||||
| Delete all posts | Delete all posts | disabled | should |
|
||||
| Remove all forum tags | Forum tags have been deleted | enabled | should not |
|
||||
|
||||
@javascript
|
||||
Scenario Outline: Delete glossary entry tags using course reuse
|
||||
Given the following "mod_glossary > entries" exist:
|
||||
| glossary | concept | definition | user | tags |
|
||||
| Test Glossary | Aubergine | Also eggpgplant | admin | SampleTag, GlossaryTag |
|
||||
# Perform course reset without checking anything.
|
||||
And I am on the "Course 1" "reset" page logged in as admin
|
||||
And I press "Reset"
|
||||
And I press "Continue"
|
||||
# Confirm that glossary entry tags are not deleted.
|
||||
When I am on the "Test Glossary" "glossary activity" page
|
||||
Then I should see "SampleTag"
|
||||
And I should see "GlossaryTag"
|
||||
And I am on the "Course 1" "reset" page
|
||||
And I expand all fieldsets
|
||||
# Depending on <resetcheck> value, either delete all glossary entries or remove all glossary entry tags only.
|
||||
And I click on "<resetcheck>" "checkbox"
|
||||
# Confirm `Remove all forum tags` is disabled when `Delete entries from all glossaries` on previous step is checked.
|
||||
And the "Remove all glossary tags" "checkbox" should be <canbechecked>
|
||||
And I press "Reset"
|
||||
And I should see "<resetmessage>" in the "Glossaries" "table_row"
|
||||
And I press "Continue"
|
||||
And I am on the "Test Glossary" "glossary activity" page
|
||||
# Confirm glossary entries are deleted when `Delete entries from all glossaries` is checked.
|
||||
And I <glossaryview> see "No entries found in this section"
|
||||
# Confirm that glossary entry tags are deleted.
|
||||
And I should not see "SampleTag"
|
||||
And I should not see "GlossaryTag"
|
||||
|
||||
Examples:
|
||||
| resetcheck | resetmessage | canbechecked | glossaryview |
|
||||
| Delete entries from all glossaries | Delete entries from all glossaries | disabled | should |
|
||||
| Remove all glossary tags | Glossary tags have been deleted | enabled | should not |
|
||||
@@ -39,8 +39,7 @@ require_once(__DIR__.'/lib.php');
|
||||
global $CFG, $DB, $PAGE, $USER;
|
||||
require_once($CFG->libdir . '/filelib.php');
|
||||
require_login(null, false);
|
||||
|
||||
confirm_sesskey();
|
||||
require_sesskey();
|
||||
$launchid = required_param('launchid', PARAM_TEXT);
|
||||
$modules = optional_param_array('modules', [], PARAM_INT);
|
||||
$grades = optional_param_array('grades', [], PARAM_INT);
|
||||
|
||||
@@ -328,8 +328,9 @@ class externallib_test extends externallib_advanced_testcase {
|
||||
$this->expectExceptionMessage($exception['message']);
|
||||
} else {
|
||||
// Failed, only canview and exception are supported.
|
||||
$this->markTestIncomplete('Incomplete, only canview and exception are supported');
|
||||
throw new \coding_exception('Incomplete, only canview and exception are supported');
|
||||
}
|
||||
|
||||
// Switch to the user and assign the role.
|
||||
$this->setUser(${$user});
|
||||
role_assign($roleid, $USER->id, $coursecontext);
|
||||
|
||||
+2
-10
@@ -835,16 +835,8 @@ class grade_edit_tree_column_name extends grade_edit_tree_column {
|
||||
|
||||
$itemicon = \html_writer::div($params['icon'], 'mr-1');
|
||||
$itemtype = \html_writer::span($params['type'], 'd-block text-uppercase small dimmed_text');
|
||||
|
||||
// Generate the content for a cell that represents a grade item.
|
||||
// If a behat test site is running avoid outputting the information about the type of the grade item.
|
||||
// This additional information causes issues in behat particularly with the existing xpath used to
|
||||
// interact with table elements.
|
||||
if (!defined('BEHAT_SITE_RUNNING')) {
|
||||
$content = \html_writer::div($itemtype . $params['name']);
|
||||
} else {
|
||||
$content = \html_writer::div($params['name']);
|
||||
}
|
||||
$itemtitle = html_writer::div($params['name'], 'rowtitle');
|
||||
$content = \html_writer::div($itemtype . $itemtitle);
|
||||
|
||||
$moveaction = isset($params['moveaction']) ? $params['moveaction'] : '';
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
@gradingform @gradingform_guide
|
||||
Feature: Teacher can define a marking guide
|
||||
As a teacher,
|
||||
I should be able to define a marking guide
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firtname | lastname | email |
|
||||
| teacher1 | Teacher | One | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "activities" exist:
|
||||
| activity | course | name | advancedgradingmethod_submissions |
|
||||
| assign | C1 | Assign 1 | guide |
|
||||
And I am on the "Course 1" course page logged in as teacher1
|
||||
And I go to "Assign 1" advanced grading definition page
|
||||
And I set the following fields to these values:
|
||||
| Name | Marking guide 1 |
|
||||
|
||||
Scenario: No criterion added to marking guide
|
||||
When I press "Save as draft"
|
||||
# Confirm that criterion parameters are required
|
||||
Then I should see "Criterion name can not be empty"
|
||||
And I should see "Criterion max score can not be empty"
|
||||
# Confirm that marking guide is not saved due to the missing criterion
|
||||
And I should not see "Marking guide 1 Draft"
|
||||
And I should not see "Please note: the advanced grading form is not ready at the moment. Simple grading method will be used until the form has a valid status."
|
||||
|
||||
@javascript
|
||||
Scenario: Marking guide criterion is added to marking guide
|
||||
Given I define the following marking guide:
|
||||
| Criterion name | Description for students | Description for markers | Maximum score |
|
||||
| Criteria 1 | Criteria 1 description for student | Criteria 1 description for marker | 70 |
|
||||
| Criteria 2 | Criteria 2 description for student | Criteria 2 description for marker | 30 |
|
||||
# Move Criteria 1 below Criteria 2
|
||||
And I click on "Move down" "button" in the "Criteria 1" "table_row"
|
||||
When I press "Save as draft"
|
||||
And I go to "Assign 1" advanced grading definition page
|
||||
# Confirm that the order of criterion shown matches input -- Criteria 2 is listed before Criteria 1
|
||||
Then "Move down" "button" in the "Criteria 2" "table_row" should be visible
|
||||
And "Move up" "button" in the "Criteria 2" "table_row" should not be visible
|
||||
And "Move up" "button" in the "Criteria 1" "table_row" should be visible
|
||||
And "Move down" "button" in the "Criteria 1" "table_row" should not be visible
|
||||
# Confirm the other information entered were saved
|
||||
And I should see "Criteria 2 description for student" in the "Criteria 2" "table_row"
|
||||
And I should see "Criteria 2 description for marker" in the "Criteria 2" "table_row"
|
||||
And I should see "30" in the "Criteria 2" "table_row"
|
||||
And I should see "Criteria 1 description for student" in the "Criteria 1" "table_row"
|
||||
And I should see "Criteria 1 description for marker" in the "Criteria 1" "table_row"
|
||||
And I should see "70" in the "Criteria 1" "table_row"
|
||||
|
||||
Scenario: Marking guide options and frequently used comment are added to marking guide
|
||||
Given I define the following marking guide:
|
||||
| Criterion name | Description for students | Description for markers | Maximum score |
|
||||
| Criteria 1 | Criteria 1 description for student | Criteria 1 description for marker | 50 |
|
||||
| Criteria 2 | Criteria 2 description for student | Criteria 2 description for marker | 50 |
|
||||
# Add frequently used comments and other marking guide options
|
||||
And I define the following frequently used comments:
|
||||
| Comment 1 |
|
||||
| Comment 2 |
|
||||
And I set the following fields to these values:
|
||||
| Show guide definition to students | 1 |
|
||||
| Show marks per criterion to students | 0 |
|
||||
When I press "Save as draft"
|
||||
And I go to "Assign 1" advanced grading definition page
|
||||
# Confirm that frequently used comments and marking guide options specified during registration are retained
|
||||
Then I should see "Comment 1"
|
||||
And I should see "Comment 2"
|
||||
And the field "Show guide definition to students" matches value "1"
|
||||
And the field "Show marks per criterion to students" matches value "0"
|
||||
@@ -190,14 +190,9 @@ class user extends tablelike implements selectable_items {
|
||||
$itemicon = html_writer::div($this->format_icon($item), 'mr-1');
|
||||
$itemtype = \html_writer::span($this->structure->get_element_type_string($gradetreeitem),
|
||||
'd-block text-uppercase small dimmed_text');
|
||||
// If a behat test site is running avoid outputting the information about the type of the grade item.
|
||||
// This additional information currently causes issues in behat particularly with the existing xpath used to
|
||||
// interact with table elements.
|
||||
if (!defined('BEHAT_SITE_RUNNING')) {
|
||||
$itemcontent = html_writer::div($itemtype . $itemname);
|
||||
} else {
|
||||
$itemcontent = html_writer::div($itemname);
|
||||
}
|
||||
|
||||
$itemtitle = html_writer::div($itemname, 'rowtitle');
|
||||
$itemcontent = html_writer::div($itemtype . $itemtitle);
|
||||
|
||||
$line = [
|
||||
html_writer::div($itemicon . $itemcontent . $lockicon, "{$type} d-flex align-items-center"),
|
||||
|
||||
@@ -551,14 +551,8 @@ class user extends grade_report {
|
||||
}
|
||||
|
||||
// Generate the content for a cell that represents a grade item.
|
||||
// If a behat test site is running avoid outputting the information about the type of the grade item.
|
||||
// This additional information causes issues in behat particularly with the existing xpath used to
|
||||
// interact with table elements.
|
||||
if (!defined('BEHAT_SITE_RUNNING')) {
|
||||
$content = \html_writer::div($itemtype . $fullname);
|
||||
} else {
|
||||
$content = \html_writer::div($fullname);
|
||||
}
|
||||
$itemtitle = \html_writer::div($fullname, 'rowtitle');
|
||||
$content = \html_writer::div($itemtype . $itemtitle);
|
||||
|
||||
// Name.
|
||||
$data['itemname']['content'] = \html_writer::div($itemicon . $content, "{$type} d-flex align-items-center");
|
||||
|
||||
+1
-4
@@ -478,7 +478,7 @@ if ($config->stage == INSTALL_DATABASE) {
|
||||
if ($hint_database !== '') {
|
||||
echo '<div class="alert alert-danger">'.$hint_database.'</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
install_print_footer($config);
|
||||
die;
|
||||
}
|
||||
@@ -610,11 +610,8 @@ if ($config->stage == INSTALL_PATHS) {
|
||||
if ($hint_admindir !== '') {
|
||||
echo '<div class="alert alert-danger">'.$hint_admindir.'</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
install_print_footer($config);
|
||||
die;
|
||||
}
|
||||
|
||||
@@ -39,4 +39,7 @@ $string['cannotdownloadzipfile'] = 'Не може да се изтегли ZIP
|
||||
$string['cannotfindcomponent'] = 'Не можа да намери компонент';
|
||||
$string['cannotunzipfile'] = 'Файлът не може да се разархивира';
|
||||
$string['componentisuptodate'] = 'Компонентът е актуален';
|
||||
$string['remotedownloaderror'] = 'Изтеглянето на компонента към вашия сървър пропадна, проверете настройките на proxy, препоръчително е PHP разширението cURL.<br /><br />Вие трябва ръчно да изтеглите файла <a href="{$a->url}">{$a->url}</a>, да го копирате в директория {$a->dest} на вашия сървър и да го разархивирате там.';
|
||||
$string['remotedownloaderror'] = '<p>Изтеглянето на компонента към вашия сървър пропадна, проверете настройките на proxy; препоръчително е PHP разширението cURL.</p><p>Вие трябва ръчно да изтеглите файла <a href="{$a->url}">{$a->url}</a>, да го копирате в директория "{$a->dest}" на вашия сървър и да го разархивирате там.</p>';
|
||||
$string['wrongdestpath'] = 'Грешен път към целта';
|
||||
$string['wrongsourcebase'] = 'Грешен изходен адрес';
|
||||
$string['wrongzipfilename'] = 'Грешно име на ZIP файл-а';
|
||||
|
||||
@@ -32,7 +32,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
$string['admindirname'] = 'Pasta de administração';
|
||||
$string['availablelangs'] = 'Pacotes linguísticos disponíveis';
|
||||
$string['chooselanguagehead'] = 'Selecione um idioma';
|
||||
$string['chooselanguagesub'] = 'Selecione o idioma a utilizar durante a instalação. O idioma escolhido será definido como o predefinido mas poderá depois selecionar outro(s) idioma(s) para o site e para os utilizadores.';
|
||||
$string['chooselanguagesub'] = 'Selecione o idioma a utilizar durante a instalação. O idioma escolhido será definido como o predefinido, mas poderá depois selecionar outro(s) idioma(s) para o site e para os utilizadores.';
|
||||
$string['clialreadyconfigured'] = 'O ficheiro config.php já existe. Use \'admin/cli/install_database.php\' para instalar o Moodle para este site.';
|
||||
$string['clialreadyinstalled'] = 'O ficheiro config.php já existe, use admin/cli/install_database.php para atualizar o Moodle para este site.';
|
||||
$string['cliinstallheader'] = 'Programa para instalação do Moodle <b>{$a}</b> através da linha de comandos';
|
||||
@@ -56,9 +56,9 @@ $string['pathshead'] = 'Confirmar caminhos';
|
||||
$string['pathsrodataroot'] = 'A pasta dos dados é só de leitura (não permite escrita).';
|
||||
$string['pathsroparentdataroot'] = 'A pasta ascendente <b>{$a->parent}</b> não tem permissões de escrita. O programa de instalação não conseguiu criar a pasta <b>{$a->dataroot}</b>.';
|
||||
$string['pathssubadmindir'] = 'Alguns servidores Web utilizam a pasta <strong>admin</strong> em URLs especiais de acesso a funcionalidades especiais, como é o caso de painéis de controlo. Algumas situações podem criar conflitos com a localização normal das páginas de administração do Moodle. Estes problemas podem ser resolvidos renomeando a pasta <strong>admin</strong> na instalação do Moodle e indicando aqui o novo nome a utilizar. Exemplo:<br /><br /><b>moodleadmin</b><br /><br />Esta ação resolverá os problemas de acesso das hiperligações para as funcionalidades de administração do Moodle.';
|
||||
$string['pathssubdataroot'] = '<p>Pasta onde o Moodle irá armazenar todo o conteúdo de ficheiros enviados pelos utilizadores.</p>
|
||||
<p>O utilizador do Moodle no servidor web (normalmente <b>nobody</b>, <b>apache</b> ou <b>www-data</b>) deve ter permissão de <b>leitura</b> e <b>escrita</b> nessa pasta.
|
||||
<p>Não deve ser acessível diretamente através da web.</p>
|
||||
$string['pathssubdataroot'] = '<p>Pasta onde o Moodle irá armazenar todo o conteúdo dos ficheiros enviados pelos utilizadores.</p>
|
||||
<p>O utilizador do Moodle no servidor web (normalmente <b>nobody</b>, <b>apache</b> ou <b>www-data</b>) deve ter permissão de <b>leitura</b> e de <b>escrita</b> nessa pasta.
|
||||
<p>Não pode ser acessível diretamente através da web.</p>
|
||||
<p>Se a pasta não existir, o processo de instalação tentará criá-la.</p>';
|
||||
$string['pathssubdirroot'] = 'Caminho completo para a pasta que contém o código Moodle.';
|
||||
$string['pathssubwwwroot'] = 'Endereço web completo de acesso ao Moodle. Não é possível aceder ao Moodle usando mais do que um endereço. Se o site tiver mais do que um endereço público, devem ser configurados redirecionamentos permanentes em todos eles, à exceção deste. Se o site pode ser acedido a partir da Internet e de Intranet, use o endereço público aqui. Se o endereço atual não está correto, altere o endereço indicado na barra de endereço do seu navegador e reinicie a instalação.';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['cliincorrectvalueretry'] = 'Noto\'g\'ri qiymat, iltimos qayta urinib ko\'ring';
|
||||
$string['clitypevalue'] = 'qiymatni kiritish';
|
||||
$string['clitypevaluedefault'] = 'qiymatni kiriting, ({$a}) odatiy qiymatni ishlatish uchun Enter tumgasini bosing';
|
||||
@@ -226,7 +226,7 @@ class behat_util extends testing_util {
|
||||
|
||||
behat_error (BEHAT_EXITCODE_REQUIREMENT, $CFG->behat_wwwroot . ' is not available, ensure you specified ' .
|
||||
'correct url and that the server is set up and started.' . PHP_EOL . ' More info in ' .
|
||||
behat_command::DOCS_URL . PHP_EOL);
|
||||
behat_command::DOCS_URL . PHP_EOL . parent::get_site_info());
|
||||
}
|
||||
|
||||
// Check if cli version is same as web version.
|
||||
|
||||
@@ -638,7 +638,10 @@ class mysql_sql_generator extends sql_generator {
|
||||
'_filename', 'admin', 'cume_dist', 'dense_rank', 'empty', 'except', 'first_value', 'grouping', 'groups',
|
||||
'json_table', 'lag', 'last_value', 'lead', 'nth_value', 'ntile',
|
||||
'of', 'over', 'percent_rank', 'persist', 'persist_only', 'rank', 'recursive', 'row_number',
|
||||
'system', 'window'
|
||||
'system', 'window',
|
||||
// Added in Amazon Aurora MySQL version 3.06.0:
|
||||
// https://docs.aws.amazon.com/AmazonRDS/latest/AuroraMySQLReleaseNotes/AuroraMySQL.Updates.3060.html .
|
||||
'accept', 'aws_bedrock_invoke_model', 'aws_sagemaker_invoke_endpoint', 'content_type', 'timeout_ms',
|
||||
);
|
||||
return $reserved_words;
|
||||
}
|
||||
|
||||
@@ -3847,6 +3847,7 @@ class curl {
|
||||
|
||||
$redirects++;
|
||||
|
||||
$currenturl = $redirecturl ?? $url;
|
||||
$redirecturl = null;
|
||||
if (isset($this->info['redirect_url'])) {
|
||||
if (preg_match('|^https?://|i', $this->info['redirect_url'])) {
|
||||
@@ -3904,6 +3905,12 @@ class curl {
|
||||
}
|
||||
|
||||
curl_setopt($curl, CURLOPT_URL, $redirecturl);
|
||||
|
||||
if (parse_url($currenturl)['host'] !== parse_url($redirecturl)['host']) {
|
||||
curl_setopt($curl, CURLOPT_HTTPAUTH, null);
|
||||
curl_setopt($curl, CURLOPT_USERPWD, null);
|
||||
}
|
||||
|
||||
$ret = curl_exec($curl);
|
||||
|
||||
$this->info = curl_getinfo($curl);
|
||||
|
||||
@@ -1456,8 +1456,8 @@ EOF;
|
||||
// Check if value exists in specific row/column.
|
||||
// Get row xpath.
|
||||
// GoutteDriver uses DomCrawler\Crawler and it is making XPath relative to the current context, so use descendant.
|
||||
$rowxpath = $tablexpath."/tbody/tr[descendant::th[normalize-space(.)=" . $rowliteral .
|
||||
"] | descendant::td[normalize-space(.)=" . $rowliteral . "]]";
|
||||
$rowxpath = $tablexpath . "/tbody/tr[descendant::*[@class='rowtitle'][normalize-space(.)=" . $rowliteral . "] | " . "
|
||||
descendant::th[normalize-space(.)=" . $rowliteral . "] | descendant::td[normalize-space(.)=" . $rowliteral . "]]";
|
||||
|
||||
$columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]";
|
||||
|
||||
|
||||
+119
-33
@@ -24,8 +24,7 @@
|
||||
*
|
||||
* @covers \core_component
|
||||
*/
|
||||
class component_test extends advanced_testcase {
|
||||
|
||||
final class component_test extends advanced_testcase {
|
||||
/**
|
||||
* To be changed if number of subsystems increases/decreases,
|
||||
* this is defined here to annoy devs that try to add more without any thinking,
|
||||
@@ -474,8 +473,10 @@ class component_test extends advanced_testcase {
|
||||
$this->assertEquals(array(), array_keys($list));
|
||||
}
|
||||
|
||||
public function test_get_component_classes_in_namespace() {
|
||||
|
||||
/**
|
||||
* Tests for get_component_classes_in_namespace.
|
||||
*/
|
||||
public function test_get_component_classes_in_namespace(): void {
|
||||
// Unexisting.
|
||||
$this->assertCount(0, core_component::get_component_classes_in_namespace('core_unexistingcomponent', 'something'));
|
||||
$this->assertCount(0, core_component::get_component_classes_in_namespace('auth_cas', 'something'));
|
||||
@@ -485,35 +486,6 @@ class component_test extends advanced_testcase {
|
||||
$this->assertCount(0, core_component::get_component_classes_in_namespace('core_user', 'course'));
|
||||
$this->assertCount(0, core_component::get_component_classes_in_namespace('mod_forum', 'output\\emaildigest'));
|
||||
$this->assertCount(0, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\emaildigest'));
|
||||
$this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', 'output\\email'));
|
||||
$this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\email'));
|
||||
$this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', 'output\\email\\'));
|
||||
$this->assertCount(2, core_component::get_component_classes_in_namespace('mod_forum', '\\output\\email\\'));
|
||||
|
||||
// Prefix with backslash if it doesn\'t come prefixed.
|
||||
$this->assertCount(1, core_component::get_component_classes_in_namespace('auth_cas', 'task'));
|
||||
$this->assertCount(1, core_component::get_component_classes_in_namespace('auth_cas', '\\task'));
|
||||
|
||||
// Core as a component works, the function can normalise the component name.
|
||||
$this->assertCount(7, core_component::get_component_classes_in_namespace('core', 'update'));
|
||||
$this->assertCount(7, core_component::get_component_classes_in_namespace('', 'update'));
|
||||
$this->assertCount(7, core_component::get_component_classes_in_namespace('moodle', 'update'));
|
||||
|
||||
// Multiple levels.
|
||||
$this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', '\\output\\myprofile\\'));
|
||||
$this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', 'output\\myprofile\\'));
|
||||
$this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', '\\output\\myprofile'));
|
||||
$this->assertCount(5, core_component::get_component_classes_in_namespace('core_user', 'output\\myprofile'));
|
||||
|
||||
// Without namespace it returns classes/ classes.
|
||||
$this->assertCount(6, core_component::get_component_classes_in_namespace('tool_mobile', ''));
|
||||
$this->assertCount(2, core_component::get_component_classes_in_namespace('tool_filetypes'));
|
||||
|
||||
// When no component is specified, classes are returned for the namespace in all components.
|
||||
// (We don't assert exact amounts here as the count of `output` classes will change depending on plugins installed).
|
||||
$this->assertGreaterThan(
|
||||
count(\core_component::get_component_classes_in_namespace('core', 'output')),
|
||||
count(\core_component::get_component_classes_in_namespace(null, 'output')));
|
||||
|
||||
// Without either a component or namespace it returns an empty array.
|
||||
$this->assertEmpty(\core_component::get_component_classes_in_namespace());
|
||||
@@ -521,6 +493,120 @@ class component_test extends advanced_testcase {
|
||||
$this->assertEmpty(\core_component::get_component_classes_in_namespace(null, ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the get_component_classes_in_namespace() function returns classes in the correct namespace.
|
||||
*
|
||||
* @dataProvider get_component_classes_in_namespace_provider
|
||||
* @param array $methodargs
|
||||
* @param string $expectedclassnameformat
|
||||
*/
|
||||
public function test_get_component_classes_in_namespace_provider(
|
||||
array $methodargs,
|
||||
string $expectedclassnameformat,
|
||||
): void {
|
||||
$classlist = core_component::get_component_classes_in_namespace(...$methodargs);
|
||||
$this->assertGreaterThan(0, count($classlist));
|
||||
|
||||
foreach (array_keys($classlist) as $classname) {
|
||||
$this->assertStringMatchesFormat($expectedclassnameformat, $classname);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for get_component_classes_in_namespace tests.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_component_classes_in_namespace_provider(): array {
|
||||
return [
|
||||
// Matches the last namespace level name not partials.
|
||||
[
|
||||
['mod_forum', 'output\\email'],
|
||||
'mod_forum\output\email\%s',
|
||||
],
|
||||
[
|
||||
['mod_forum', '\\output\\email'],
|
||||
'mod_forum\output\email\%s',
|
||||
],
|
||||
[
|
||||
['mod_forum', 'output\\email\\'],
|
||||
'mod_forum\output\email\%s',
|
||||
],
|
||||
[
|
||||
['mod_forum', '\\output\\email\\'],
|
||||
'mod_forum\output\email\%s',
|
||||
],
|
||||
// Prefix with backslash if it doesn\'t come prefixed.
|
||||
[
|
||||
['auth_cas', 'task'],
|
||||
'auth_cas\task\%s',
|
||||
],
|
||||
[
|
||||
['auth_cas', '\\task'],
|
||||
'auth_cas\task\%s',
|
||||
],
|
||||
|
||||
// Core as a component works, the function can normalise the component name.
|
||||
[
|
||||
['core', 'update'],
|
||||
'core\update\%s',
|
||||
],
|
||||
[
|
||||
['', 'update'],
|
||||
'core\update\%s',
|
||||
],
|
||||
[
|
||||
['moodle', 'update'],
|
||||
'core\update\%s',
|
||||
],
|
||||
|
||||
// Multiple levels.
|
||||
[
|
||||
['core_user', '\\output\\myprofile\\'],
|
||||
'core_user\output\myprofile\%s',
|
||||
],
|
||||
[
|
||||
['core_user', 'output\\myprofile\\'],
|
||||
'core_user\output\myprofile\%s',
|
||||
],
|
||||
[
|
||||
['core_user', '\\output\\myprofile'],
|
||||
'core_user\output\myprofile\%s',
|
||||
],
|
||||
[
|
||||
['core_user', 'output\\myprofile'],
|
||||
'core_user\output\myprofile\%s',
|
||||
],
|
||||
|
||||
// Without namespace it returns classes/ classes.
|
||||
[
|
||||
['tool_mobile', ''],
|
||||
'tool_mobile\%s',
|
||||
],
|
||||
[
|
||||
['tool_filetypes'],
|
||||
'tool_filetypes\%s',
|
||||
],
|
||||
|
||||
// Multiple levels.
|
||||
[
|
||||
['core_user', '\\output\\myprofile\\'],
|
||||
'core_user\output\myprofile\%s',
|
||||
],
|
||||
|
||||
// When no component is specified, classes are returned for the namespace in all components.
|
||||
// (We don't assert exact amounts here as the count of `output` classes will change depending on plugins installed).
|
||||
[
|
||||
['core', 'output'],
|
||||
'core\%s',
|
||||
],
|
||||
[
|
||||
[null, 'output'],
|
||||
'%s',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for classloader test
|
||||
*/
|
||||
|
||||
@@ -396,6 +396,7 @@ class feedback_test extends \advanced_testcase {
|
||||
* and false when not modified.
|
||||
*/
|
||||
public function test_is_feedback_modified() {
|
||||
$this->require_ghostscript();
|
||||
$this->resetAfterTest();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
|
||||
|
||||
+14
-12
@@ -9504,20 +9504,22 @@ class assign {
|
||||
$confirm = optional_param('confirm', 0, PARAM_BOOL);
|
||||
|
||||
if ($confirm) {
|
||||
confirm_sesskey();
|
||||
|
||||
// Fix the grades.
|
||||
$this->fix_null_grades();
|
||||
unset_config('has_rescaled_null_grades_' . $instance->id, 'assign');
|
||||
|
||||
// Display the notice.
|
||||
$o .= $this->get_renderer()->notification(get_string('fixrescalednullgradesdone', 'assign'), 'notifysuccess');
|
||||
if (confirm_sesskey()) {
|
||||
// Fix the grades.
|
||||
$this->fix_null_grades();
|
||||
unset_config('has_rescaled_null_grades_' . $instance->id, 'assign');
|
||||
// Display the success notice.
|
||||
$o .= $this->get_renderer()->notification(get_string('fixrescalednullgradesdone', 'assign'), 'notifysuccess');
|
||||
} else {
|
||||
// If the sesskey is not valid, then display the error notice.
|
||||
$o .= $this->get_renderer()->notification(get_string('invalidsesskey', 'error'), 'notifyerror');
|
||||
}
|
||||
$url = new moodle_url(
|
||||
'/mod/assign/view.php',
|
||||
array(
|
||||
url: '/mod/assign/view.php',
|
||||
params: [
|
||||
'id' => $this->get_course_module()->id,
|
||||
'action' => 'grading'
|
||||
)
|
||||
'action' => 'grading',
|
||||
],
|
||||
);
|
||||
$o .= $this->get_renderer()->continue_button($url);
|
||||
} else {
|
||||
|
||||
@@ -55,6 +55,8 @@ class get_join_url extends external_api {
|
||||
* @param int $cmid the bigbluebuttonbn course module id
|
||||
* @param null|int $groupid
|
||||
* @return array (empty array for now)
|
||||
*
|
||||
* @throws restricted_context_exception
|
||||
*/
|
||||
public static function execute(
|
||||
int $cmid,
|
||||
@@ -81,7 +83,11 @@ class get_join_url extends external_api {
|
||||
}
|
||||
$instance->set_group_id($groupid);
|
||||
|
||||
// Validate that the user has access to this activity and to join the meeting.
|
||||
self::validate_context($instance->get_context());
|
||||
if (!$instance->can_join()) {
|
||||
throw new restricted_context_exception();
|
||||
}
|
||||
|
||||
try {
|
||||
$result['join_url'] = meeting::join_meeting($instance);
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
namespace mod_bigbluebuttonbn\external;
|
||||
|
||||
use context_course;
|
||||
use core_external\external_api;
|
||||
use core_external\restricted_context_exception;
|
||||
use mod_bigbluebuttonbn\instance;
|
||||
use mod_bigbluebuttonbn\test\testcase_helper_trait;
|
||||
use moodle_exception;
|
||||
@@ -85,6 +87,28 @@ class get_join_url_test extends \externallib_advanced_testcase {
|
||||
$this->get_join_url($instance->get_cm_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test execution with a user who doesn't have the capability to join the meeting
|
||||
*/
|
||||
public function test_execute_without_capability(): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$record = $this->getDataGenerator()->create_module('bigbluebuttonbn', ['course' => $course->id]);
|
||||
$instance = instance::get_from_instanceid($record->id);
|
||||
|
||||
$user = $this->getDataGenerator()->create_and_enrol($course);
|
||||
$this->setUser($user);
|
||||
|
||||
$student = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
|
||||
assign_capability('mod/bigbluebuttonbn:join', CAP_PROHIBIT, $student, context_course::instance($course->id), true);
|
||||
|
||||
$this->expectException(restricted_context_exception::class);
|
||||
$this->get_join_url($instance->get_cm_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test execute API CALL with invalid login
|
||||
*/
|
||||
|
||||
+4
-1
@@ -3277,7 +3277,10 @@ function forum_add_discussion($discussion, $mform=null, $unused=null, $userid=nu
|
||||
}
|
||||
|
||||
if (isset($discussion->tags)) {
|
||||
core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id, context_module::instance($cm->id), $discussion->tags);
|
||||
$tags = is_array($discussion->tags) ? $discussion->tags : explode(',', $discussion->tags);
|
||||
|
||||
core_tag_tag::set_item_tags('mod_forum', 'forum_posts', $post->id,
|
||||
context_module::instance($cm->id), $tags);
|
||||
}
|
||||
|
||||
if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
@mod @mod_glossary
|
||||
Feature: Glossary can be set to various display formats
|
||||
In order to display different glossary formats
|
||||
As a teacher
|
||||
I can set the glossary activity display format
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | One | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
Given the following "activities" exist:
|
||||
| activity | course | name |
|
||||
| glossary | C1 | Glossary 1 |
|
||||
And the following "mod_glossary > entries" exist:
|
||||
| glossary | concept | definition |
|
||||
| Glossary 1 | Entry 1 | Entry 1 definition |
|
||||
| Glossary 1 | Entry 2 | Entry 2 definition |
|
||||
|
||||
Scenario: Glossary display format is entry list style
|
||||
Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1
|
||||
And I set the following fields to these values:
|
||||
| displayformat | entrylist |
|
||||
When I press "Save and display"
|
||||
# Confirm that glossary display format is entry list.
|
||||
# In this format, the concept definitions are not displayed.
|
||||
Then I should not see "by Admin User"
|
||||
And I should not see "Entry 1 definition"
|
||||
And I should not see "Entry 2 definition"
|
||||
And ".entrylist" "css_element" should exist
|
||||
|
||||
Scenario: Glossary display format is FAQ-style
|
||||
Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1
|
||||
And I set the following fields to these values:
|
||||
| displayformat | faq |
|
||||
When I press "Save and display"
|
||||
# Confirm that glossary format is FAQ.
|
||||
# In this format, the words Question and Answer are displayed.
|
||||
Then I should see "Question:"
|
||||
And I should see "Answer:"
|
||||
And ".faq" "css_element" should exist
|
||||
|
||||
@_file_upload @javascript
|
||||
Scenario: Glossary display format is full without author style
|
||||
Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1
|
||||
And I set the following fields to these values:
|
||||
| displayformat | fullwithoutauthor |
|
||||
And I press "Save and display"
|
||||
And I press "Add entry"
|
||||
# Add an entry with an attachment.
|
||||
And I set the following fields to these values:
|
||||
| Concept | Entry 3 |
|
||||
| Definition | Entry 3 definition |
|
||||
| Attachment | lib/tests/fixtures/gd-logo.png |
|
||||
When I press "Save changes"
|
||||
# Confirm that glossary format is full without author style.
|
||||
# In this format, the image link should exist and author's name should not be visible.
|
||||
Then "gd-logo.png" "link" should exist
|
||||
And I should not see "by Admin User"
|
||||
And ".fullwithoutauthor" "css_element" should exist
|
||||
|
||||
@_file_upload @javascript
|
||||
Scenario: Glossary display format is encyclopedia style
|
||||
Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1
|
||||
And I set the following fields to these values:
|
||||
| displayformat | encyclopedia |
|
||||
And I press "Save and display"
|
||||
And I press "Add entry"
|
||||
# Add an entry with an attachment.
|
||||
And I set the following fields to these values:
|
||||
| Concept | Entry 3 |
|
||||
| Definition | Entry 3 definition |
|
||||
| Attachment | lib/tests/fixtures/gd-logo.png |
|
||||
When I press "Save changes"
|
||||
# Confirm that glossary format is encyclopedia.
|
||||
# In this format, the image element should be displayed.
|
||||
Then "//img[contains(@src, 'gd-logo.png')]" "xpath_element" should exist
|
||||
And ".encyclopedia" "css_element" should exist
|
||||
|
||||
Scenario Outline: Glossary display format can be set to dictionary, continuous and full with author
|
||||
Given I am on the "Glossary 1" "glossary activity editing" page logged in as teacher1
|
||||
# Assign the corresponding display format to glossary activity.
|
||||
And I set the following fields to these values:
|
||||
| displayformat | <display_format> |
|
||||
When I press "Save and display"
|
||||
# Confirm that glossary format is the display format set in the previous step.
|
||||
Then I should <visibility> "by Admin User"
|
||||
And ".<display_format>" "css_element" should exist
|
||||
|
||||
Examples:
|
||||
| display_format | visibility |
|
||||
| dictionary | not see |
|
||||
| continuous | not see |
|
||||
| fullwithauthor | see |
|
||||
@@ -188,6 +188,16 @@ class mod_glossary_generator extends testing_module_generator {
|
||||
$DB->insert_record('glossary_entries_categories', ['entryid' => $id, 'categoryid' => $categoryid]);
|
||||
}
|
||||
|
||||
return $DB->get_record('glossary_entries', array('id' => $id), '*', MUST_EXIST);
|
||||
$entries = $DB->get_record('glossary_entries', ['id' => $id], '*', MUST_EXIST);
|
||||
|
||||
if (isset($record['tags'])) {
|
||||
$cm = get_coursemodule_from_instance('glossary', $glossary->id);
|
||||
$tags = is_array($record['tags']) ? $record['tags'] : explode(',', $record['tags']);
|
||||
|
||||
core_tag_tag::set_item_tags('mod_glossary', 'glossary_entries', $id,
|
||||
context_module::instance($cm->id), $tags);
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
@mod @mod_h5pactivity @core_5hp
|
||||
Feature: Control H5P activity availability for students
|
||||
In order to restrict student access to H5P activity
|
||||
As a teacher
|
||||
I need to control the availability of the H5P activity
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
And the following "activities" exist:
|
||||
| activity | course | name |
|
||||
| h5pactivity | C1 | H5P Test |
|
||||
|
||||
@javascript
|
||||
Scenario Outline: Restrict H5P activity access by date
|
||||
Given I am on the "H5P Test" "h5pactivity activity editing" page logged in as teacher1
|
||||
And I expand all fieldsets
|
||||
And I click on "Add restriction..." "button"
|
||||
And I click on "Date" "button" in the "Add restriction..." "dialogue"
|
||||
And I set the following fields to these values:
|
||||
| Direction | from |
|
||||
| x[day] | 1 |
|
||||
| x[month] | 1 |
|
||||
| x[year] | <year> |
|
||||
And I press "Save and return to course"
|
||||
When I am on the "Course 1" course page logged in as student1
|
||||
Then I <fromvisibility> see "Available from"
|
||||
|
||||
Examples:
|
||||
| year | fromvisibility |
|
||||
| ## -1 year ## %Y ## | should not |
|
||||
| ## +1 year ## %Y ## | should |
|
||||
@@ -0,0 +1,100 @@
|
||||
@mod @mod_h5pactivity @core_h5p
|
||||
Feature: Teacher can control h5p activity grading setting
|
||||
In order to set h5p activity grade
|
||||
As a teacher
|
||||
I need to be able to control h5p activity grading setting
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
| student2 | Student | 2 | student2@example.com |
|
||||
| student3 | Student | 3 | student3@example.com |
|
||||
| student4 | Student | 4 | student4@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
| student2 | C1 | student |
|
||||
| student3 | C1 | student |
|
||||
| student4 | C1 | student |
|
||||
|
||||
@javascript
|
||||
Scenario: Verify that invalid grades given to students are not saved
|
||||
Given the following "activities" exist:
|
||||
| activity | name | course |
|
||||
| h5pactivity | H5P point | C1 |
|
||||
# Activity grade settings are not saved using generators so manual setting is necessary.
|
||||
And I am on the "H5P point" "h5pactivity activity editing" page logged in as teacher1
|
||||
And I set the following fields to these values:
|
||||
| grade[modgrade_type] | Point |
|
||||
| grade[modgrade_point] | 10 |
|
||||
And I press "Save and return to course"
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I turn editing mode on
|
||||
When I give the grade "50" to the user "Student 1" for the grade item "H5P point"
|
||||
And I press "Save changes"
|
||||
And I turn editing mode off
|
||||
# Confirm that grades are not saved when grade entered is > maximum grade.
|
||||
Then the following should exist in the "user-grades" table:
|
||||
| -1- | -1- | -3- |
|
||||
| Student 1 | student1@example.com | - |
|
||||
| Student 2 | student2@example.com | - |
|
||||
| Student 3 | student3@example.com | - |
|
||||
| Student 4 | student4@example.com | - |
|
||||
|
||||
@javascript
|
||||
Scenario: Verify that valid grades given to students are saved
|
||||
Given the following "activities" exist:
|
||||
| activity | name | course |
|
||||
| h5pactivity | H5P point | C1 |
|
||||
# Activity grade settings are not saved using generators so manual setting is necessary.
|
||||
And I am on the "H5P point" "h5pactivity activity editing" page logged in as teacher1
|
||||
And I set the following fields to these values:
|
||||
| grade[modgrade_type] | Point |
|
||||
| grade[modgrade_point] | 10 |
|
||||
And I press "Save and return to course"
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I turn editing mode on
|
||||
When I give the grade "10" to the user "Student 1" for the grade item "H5P point"
|
||||
And I give the grade "5" to the user "Student 2" for the grade item "H5P point"
|
||||
And I give the grade "0" to the user "Student 3" for the grade item "H5P point"
|
||||
And I press "Save changes"
|
||||
And I turn editing mode off
|
||||
# Confirm that corresponding grades are stored for each student.
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- | -1- | -3- |
|
||||
| Student 1 | student1@example.com | 10 |
|
||||
| Student 2 | student2@example.com | 5 |
|
||||
| Student 3 | student3@example.com | 0 |
|
||||
| Student 4 | student4@example.com | - |
|
||||
|
||||
@javascript
|
||||
Scenario: Verify that scales given to students are saved
|
||||
Given the following "activities" exist:
|
||||
| activity | name | course |
|
||||
| h5pactivity | H5P scale | C1 |
|
||||
# Activity grade settings are not saved using generators so manual setting is necessary.
|
||||
And I am on the "H5P scale" "h5pactivity activity editing" page logged in as teacher1
|
||||
And I set the following fields to these values:
|
||||
| grade[modgrade_type] | Scale |
|
||||
| grade[modgrade_scale] | Default competence scale |
|
||||
And I press "Save and return to course"
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I turn editing mode on
|
||||
And I give the grade "Not yet competent" to the user "Student 1" for the grade item "H5P scale"
|
||||
And I give the grade "Competent" to the user "Student 2" for the grade item "H5P scale"
|
||||
And I give the grade "Competent" to the user "Student 4" for the grade item "H5P scale"
|
||||
When I press "Save changes"
|
||||
And I turn editing mode off
|
||||
# Confirm that scale set for student is successfully saved.
|
||||
Then the following should exist in the "user-grades" table:
|
||||
| -1- | -1- | -3- |
|
||||
| Student 1 | student1@example.com | Not yet competent |
|
||||
| Student 2 | student2@example.com | Competent |
|
||||
| Student 3 | student3@example.com | - |
|
||||
| Student 4 | student4@example.com | Competent |
|
||||
@@ -0,0 +1,106 @@
|
||||
@core @core_my @javascript
|
||||
Feature: Navigate and use preferences page
|
||||
In order to navigate through preferences page
|
||||
As a user
|
||||
I need to be able to use preferences page
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Sam | Student | s1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | format |
|
||||
| Course 1 | C1 | topics |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
And I log in as "admin"
|
||||
|
||||
Scenario Outline: Navigating through user menu Preferences
|
||||
When I follow "Preferences" in the user menu
|
||||
# Click each link in the 'Preferences' page.
|
||||
And I click on "<userprefpage>" "link" in the "#page-content" "css_element"
|
||||
# Confirm that each redirected page has 'Preferences' in the breadcrumbs.
|
||||
And "Users" "link" should not exist in the ".breadcrumb" "css_element"
|
||||
Then "Preferences" "link" should exist in the ".breadcrumb" "css_element"
|
||||
# Additional confirmation that breadcrumbs is correct.
|
||||
And "<userprefpage>" "text" should exist in the ".breadcrumb" "css_element"
|
||||
# Confirm that user name and profile picture are displayed in header section.
|
||||
And I should see "Admin User" in the ".page-header-headings" "css_element"
|
||||
And ".page-header-image" "css_element" should exist in the "#page-header" "css_element"
|
||||
|
||||
Examples:
|
||||
| userprefpage |
|
||||
| Edit profile |
|
||||
| Change password |
|
||||
| Preferred language |
|
||||
| Forum preferences |
|
||||
| Editor preferences |
|
||||
| Calendar preferences |
|
||||
| Content bank preferences |
|
||||
| Message preferences |
|
||||
| Notification preferences |
|
||||
| Manage badges |
|
||||
| Badge preferences |
|
||||
| Backpack settings |
|
||||
| This user's role assignments |
|
||||
| Permissions |
|
||||
| Check permissions |
|
||||
| Blog preferences |
|
||||
| External blogs |
|
||||
| Register an external blog |
|
||||
|
||||
Scenario Outline: Navigating through course participant preferences
|
||||
Given I am on "Course 1" course homepage
|
||||
And I navigate to course participants
|
||||
And I follow "Sam Student"
|
||||
When I click on "Preferences" "link" in the "#region-main-box" "css_element"
|
||||
Then I should see "Sam Student" in the ".page-header-headings" "css_element"
|
||||
And ".page-header-image" "css_element" should exist in the "#page-header" "css_element"
|
||||
# Click each link in the 'Preferences' page.
|
||||
And I click on "<courseprefpage>" "link" in the "#page-content" "css_element"
|
||||
# Confirm that each redirected page has 'Users/{user}/Preferences' in the breadcrumbs.
|
||||
Then "Users" "link" should exist in the ".breadcrumb" "css_element"
|
||||
And "Sam Student" "link" should exist in the ".breadcrumb" "css_element"
|
||||
And "Preferences" "link" should exist in the ".breadcrumb" "css_element"
|
||||
# Additional confirmation that breadcrumbs is correct.
|
||||
And "<courseprefpage>" "text" should exist in the ".breadcrumb" "css_element"
|
||||
# Confirm that user name and profile picture are displayed in header section.
|
||||
And I should see "Sam Student" in the ".page-header-headings" "css_element"
|
||||
And ".page-header-image" "css_element" should exist in the "#page-header" "css_element"
|
||||
|
||||
Examples:
|
||||
| courseprefpage |
|
||||
| Edit profile |
|
||||
| Preferred language |
|
||||
| Forum preferences |
|
||||
| Editor preferences |
|
||||
| Calendar preferences |
|
||||
| Content bank preferences |
|
||||
| Message preferences |
|
||||
| Notification preferences |
|
||||
| This user's role assignments |
|
||||
| Permissions |
|
||||
| Check permissions |
|
||||
|
||||
Scenario: Navigation with Event monitoring enabled
|
||||
Given I navigate to "Reports > Event monitoring rules" in site administration
|
||||
And I click on "Enable" "link"
|
||||
And I press "Add a new rule"
|
||||
And I set the following fields to these values:
|
||||
| Rule name | Testing1 |
|
||||
| Area to monitor | Core |
|
||||
| Event | Allow role override |
|
||||
And I press "Save changes"
|
||||
When I follow "Preferences" in the user menu
|
||||
# Confirm that Event monitoring is visible and clickable.
|
||||
Then I should see "Miscellaneous"
|
||||
And I follow "Event monitoring"
|
||||
# Confirm that user can subscribe to new rule.
|
||||
And "Subscribe to rule \"Testing1\"" "link" should exist
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to course participants
|
||||
And I follow "Sam Student"
|
||||
And I click on "Preferences" "link" in the "#region-main-box" "css_element"
|
||||
# Confirm that admin cannot change student's event monitor subscription.
|
||||
And I should not see "Event monitoring"
|
||||
@@ -37,7 +37,7 @@ Feature: Test creating a Numerical question
|
||||
Then I should see "Numerical-001"
|
||||
|
||||
@javascript
|
||||
Scenario: Create a Numerical question with units
|
||||
Scenario: Create a Numerical question with required units
|
||||
When I am on the "Course 1" "core_question > course question bank" page logged in as teacher
|
||||
And I add a "Numerical" question filling the form with:
|
||||
| Question name | Numerical-002 |
|
||||
@@ -70,3 +70,31 @@ Feature: Test creating a Numerical question
|
||||
| id_unitgradingtypes | as a fraction (0-1) of the question grade |
|
||||
| id_multichoicedisplay | a drop-down menu |
|
||||
| id_unitsleft | on the right, for example 1.00cm or 1.00km |
|
||||
|
||||
Scenario: Create a Numerical question with optional units
|
||||
Given I am on the "Course 1" "core_question > course question bank" page logged in as teacher
|
||||
# Add the numerical question with optional units.
|
||||
And I add a "Numerical" question filling the form with:
|
||||
| Question name | Numerical Question (optional) |
|
||||
| Question text | How many meter is 1m + 20cm + 50mm? |
|
||||
| Default mark | 1 |
|
||||
| General feedback | The correct answer is 1.25m |
|
||||
| id_answer_0 | 1.25 |
|
||||
| id_tolerance_0 | 0 |
|
||||
| id_fraction_0 | 100% |
|
||||
| id_answer_1 | 125 |
|
||||
| id_tolerance_1 | 0 |
|
||||
| id_fraction_1 | 0% |
|
||||
| id_unitrole | Units are optional. |
|
||||
| id_unitsleft | on the right, for example 1.00cm or 1.00km |
|
||||
| id_unit_0 | m |
|
||||
# Confirm that the numerical question with optional units is added successfully.
|
||||
When I choose "Edit question" action for "Numerical Question (optional)" in the question bank
|
||||
Then the following fields match these values:
|
||||
| Question name | Numerical Question (optional) |
|
||||
| Question text | How many meter is 1m + 20cm + 50mm? |
|
||||
| Default mark | 1 |
|
||||
| General feedback | The correct answer is 1.25m |
|
||||
| id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. |
|
||||
| id_unitsleft | on the right, for example 1.00cm or 1.00km |
|
||||
| id_unit_0 | m |
|
||||
|
||||
@@ -55,3 +55,32 @@ Feature: Test editing a Numerical question
|
||||
Then the following fields match these values:
|
||||
| id_answer_0 | 0.00000123456789 |
|
||||
| id_tolerance_1 | 0.0000123456789 |
|
||||
|
||||
Scenario: Edit a Numerical question with optional units
|
||||
Given I am on the "Numerical for editing" "core_question > edit" page logged in as teacher
|
||||
# Edit the existing numerical question, changing the unit to optional.
|
||||
And I set the following fields to these values:
|
||||
| Question name | Numerical Question (optional) |
|
||||
| Question text | How many meter is 1m + 20cm + 50mm? |
|
||||
| Default mark | 1 |
|
||||
| General feedback | The correct answer is 1.25m |
|
||||
| id_answer_0 | 1.25 |
|
||||
| id_tolerance_0 | 0 |
|
||||
| id_fraction_0 | 100% |
|
||||
| id_answer_1 | 125 |
|
||||
| id_tolerance_1 | 0 |
|
||||
| id_fraction_1 | 0% |
|
||||
| id_unitrole | Units are optional. |
|
||||
| id_unitsleft | on the right, for example 1.00cm or 1.00km |
|
||||
| id_unit_0 | m |
|
||||
And I press "submitbutton"
|
||||
When I choose "Edit question" action for "Numerical Question (optional)" in the question bank
|
||||
# Confirm that the numerical question with optional unit is updated accordingly.
|
||||
Then the following fields match these values:
|
||||
| Question name | Numerical Question (optional) |
|
||||
| Question text | How many meter is 1m + 20cm + 50mm? |
|
||||
| Default mark | 1 |
|
||||
| General feedback | The correct answer is 1.25m |
|
||||
| id_unitrole | Units are optional. If a unit is entered, it is used to convert the response to Unit 1 before grading. |
|
||||
| id_unitsleft | on the right, for example 1.00cm or 1.00km |
|
||||
| id_unit_0 | m |
|
||||
|
||||
@@ -45,3 +45,25 @@ Feature: Preview a Numerical question
|
||||
And I press "Check"
|
||||
And I should see "Very good."
|
||||
And I should see "Mark 1#00 out of 1#00"
|
||||
|
||||
Scenario: Preview a Numerical question with optional units
|
||||
Given I am on the "Numerical-001" "core_question > edit" page logged in as teacher
|
||||
# Edit the existing numerical question, add in the optional units.
|
||||
And I set the following fields to these values:
|
||||
| Question name | Numerical Question (optional) |
|
||||
| Question text | How many meter is 1m + 20cm + 50mm? |
|
||||
| Default mark | 1 |
|
||||
| General feedback | The correct answer is 1.25m |
|
||||
| id_answer_0 | 1.25 |
|
||||
| id_tolerance_0 | 0 |
|
||||
| id_fraction_0 | 100% |
|
||||
| id_answer_1 | 125 |
|
||||
| id_tolerance_1 | 0 |
|
||||
| id_fraction_1 | 0% |
|
||||
| id_unitrole | Units are optional. |
|
||||
| id_unitsleft | on the right, for example 1.00cm or 1.00km |
|
||||
| id_unit_0 | m |
|
||||
And I press "submitbutton"
|
||||
When I choose "Preview" action for "Numerical Question (optional)" in the question bank
|
||||
# Unit is optional, so the unit select box should not be exist.
|
||||
Then "Select one unit" "select" should not exist
|
||||
|
||||
@@ -191,11 +191,13 @@ class report_log_table_log extends table_sql {
|
||||
|
||||
} else if ($eventusername) {
|
||||
if (empty($this->download)) {
|
||||
$params = array('id' => $event->userid);
|
||||
$params = ['id' => $event->userid];
|
||||
if ($event->courseid) {
|
||||
$params['course'] = $event->courseid;
|
||||
}
|
||||
$username = html_writer::link(new moodle_url('/user/view.php', $params), $eventusername);
|
||||
} else {
|
||||
$username = $eventusername;
|
||||
}
|
||||
} else {
|
||||
$username = '-';
|
||||
|
||||
@@ -16,7 +16,6 @@ Feature: In a course administration page, navigate through report page, test for
|
||||
| admin | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
|
||||
@javascript
|
||||
Scenario: Selector should be available in the course participation page
|
||||
Given I log in as "admin"
|
||||
And I am on "Course 1" course homepage
|
||||
|
||||
@@ -20,51 +20,49 @@ Feature: In a participation report, admin can filter student actions
|
||||
| student1 | C1 | student |
|
||||
And the following "activity" exists:
|
||||
| course | C1 |
|
||||
| activity | book |
|
||||
| name | Test book name |
|
||||
| idnumber | book1 |
|
||||
And the following "mod_book > chapter" exists:
|
||||
| book | Test book name |
|
||||
| title | Test chapter |
|
||||
| content | Test chapter content |
|
||||
| activity | forum |
|
||||
| name | Test forum name |
|
||||
| idnumber | forum1 |
|
||||
|
||||
@javascript
|
||||
Scenario: Filter participation report when standard log reader is enabled later
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Plugins > Logging > Manage log stores" in site administration
|
||||
And I click on "Disable" "link" in the "Standard log" "table_row"
|
||||
And I log out
|
||||
|
||||
And I am on the "Test book name" "book activity" page logged in as student1
|
||||
And I am on the "Test forum name" "forum activity" page logged in as student1
|
||||
And I log out
|
||||
|
||||
And I log in as "admin"
|
||||
And I navigate to "Plugins > Logging > Manage log stores" in site administration
|
||||
And I click on "Enable" "link" in the "Standard log" "table_row"
|
||||
|
||||
And I am on the "Test book name" "book activity" page logged in as student1
|
||||
And I am on the "Test forum name" "forum activity" page logged in as student1
|
||||
|
||||
And I am on the "Course 1" course page logged in as admin
|
||||
When I navigate to "Reports" in current page administration
|
||||
And I click on "Course participation" "link"
|
||||
And I set the field "instanceid" to "Test book name"
|
||||
And I set the field "roleid" to "Student"
|
||||
And I set the following fields to these values:
|
||||
| Activity module | Test forum name |
|
||||
| Show only | Student |
|
||||
And I press "Go"
|
||||
Then I should see "Yes (1)"
|
||||
Then the following should exist in the "reporttable" table:
|
||||
| -1- | All actions |
|
||||
| Student 1 | Yes (1) |
|
||||
|
||||
@javascript
|
||||
Scenario: Filter participation report when only standard log reader is enabled by default
|
||||
Given I am on the "Test book name" "book activity" page logged in as student1
|
||||
|
||||
And I am on the "Course 1" course page logged in as admin
|
||||
Given I am on the "Test forum name" "forum activity" page logged in as student1
|
||||
When I am on the "Course 1" course page logged in as admin
|
||||
And I navigate to "Reports" in current page administration
|
||||
And I click on "Course participation" "link"
|
||||
And I set the field "instanceid" to "Test book name"
|
||||
And I set the field "roleid" to "Student"
|
||||
And I set the following fields to these values:
|
||||
| Activity module | Test forum name |
|
||||
| Show only | Student |
|
||||
And I press "Go"
|
||||
Then I should see "Yes (1)"
|
||||
Then the following should exist in the "reporttable" table:
|
||||
| -1- | All actions |
|
||||
| Student 1 | Yes (1) |
|
||||
|
||||
@javascript
|
||||
Scenario Outline: Filter participation report by viewable roles
|
||||
Given I am on the "Course 1" course page logged in as "teacher1"
|
||||
When I navigate to "Reports" in current page administration
|
||||
@@ -72,8 +70,8 @@ Feature: In a participation report, admin can filter student actions
|
||||
# Teacher role cannot see Manager by default.
|
||||
Then "Manager" "option" should not exist in the "Show only" "select"
|
||||
And I set the following fields to these values:
|
||||
| Activity module | Test book name |
|
||||
| Show only | <role> |
|
||||
| Activity module | Test forum name |
|
||||
| Show only | <role> |
|
||||
And I press "Go"
|
||||
And I should see "<uservisible>" in the "reporttable" "table"
|
||||
And I should not see "<usernonvisible>" in the "reporttable" "table"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@report @report_participation @javascript
|
||||
Feature: Use the particiaption report to message groups of students
|
||||
@report @report_participation
|
||||
Feature: Use the participation report to message groups of students
|
||||
In order to engage with students based on participation
|
||||
As a teacher
|
||||
I need to be able to message students who have not participated in an activity
|
||||
@@ -28,6 +28,7 @@ Feature: Use the particiaption report to message groups of students
|
||||
| idnumber | book1 |
|
||||
And I am on the "Test book name" "book activity" page logged in as student1
|
||||
|
||||
@javascript
|
||||
Scenario: Message all students from the participation report
|
||||
Given I am on the "Course 1" course page logged in as teacher1
|
||||
And I navigate to "Reports" in current page administration
|
||||
@@ -42,6 +43,7 @@ Feature: Use the particiaption report to message groups of students
|
||||
And I press "Send message to 3 people"
|
||||
And I should see "Message sent to 3 people"
|
||||
|
||||
@javascript
|
||||
Scenario: Message students who have not participated in book
|
||||
Given I am on the "Course 1" course page logged in as teacher1
|
||||
And I navigate to "Reports" in current page administration
|
||||
@@ -59,7 +61,7 @@ Feature: Use the particiaption report to message groups of students
|
||||
And I press "Send message to 2 people"
|
||||
And I should see "Message sent to 2 people"
|
||||
|
||||
Scenario: Ensure no message options when messaging is disabled
|
||||
Scenario: When messaging is disabled no message options should be displayed
|
||||
Given the following config values are set as admin:
|
||||
| messaging | 0 |
|
||||
And I am on the "Course 1" course page logged in as teacher1
|
||||
|
||||
+2
-2
@@ -29,9 +29,9 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2023042407.00; // 20230424 = branching date YYYYMMDD - do not modify!
|
||||
$version = 2023042408.00; // 20230424 = branching date YYYYMMDD - do not modify!
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
$release = '4.2.7 (Build: 20240422)'; // Human-friendly version name
|
||||
$release = '4.2.8 (Build: 20240610)'; // Human-friendly version name
|
||||
$branch = '402'; // This version's branch.
|
||||
$maturity = MATURITY_STABLE; // This version's maturity level.
|
||||
|
||||
Reference in New Issue
Block a user