Compare commits
365 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4972dd43dd | |||
| 2f1ed94388 | |||
| f49a92efa5 | |||
| 7fb7ab13dc | |||
| 34d9787dec | |||
| d890ed6adf | |||
| ed10f401df | |||
| df5e6e00fb | |||
| 84eed6a5a4 | |||
| cb857db4f2 | |||
| ff2435d89c | |||
| 111bca500e | |||
| 4701196ce8 | |||
| 8d4f985112 | |||
| ac3f1bdecd | |||
| 953195bf38 | |||
| fa6a5a4b29 | |||
| 6be75c49ec | |||
| 5d9bf8fe28 | |||
| c1143b3932 | |||
| e2df9eabc5 | |||
| 42481b9ca0 | |||
| ecd0e011ee | |||
| e82c80f706 | |||
| a8c51871f4 | |||
| d5067bffd2 | |||
| a3d19efab4 | |||
| 888f55e41e | |||
| f22174be37 | |||
| eff5fd4984 | |||
| 09543c0123 | |||
| 29924b65e3 | |||
| 7819b928b1 | |||
| 151fc538de | |||
| 391d53f871 | |||
| 64f1f9ed5a | |||
| c6631a119c | |||
| 6af01fb2e4 | |||
| 83af30b285 | |||
| 37858eb4a8 | |||
| 569b5eeac1 | |||
| 43a538205f | |||
| 5557ec004c | |||
| fe3d98f0a0 | |||
| 2b9c3f1089 | |||
| e6b2cb22d0 | |||
| 6f1ffbdf1f | |||
| 65faf38134 | |||
| a3b8e03c1c | |||
| daea8b596f | |||
| 84d3af595b | |||
| a402077cf2 | |||
| d199355889 | |||
| bdf593e235 | |||
| 2ca8477af4 | |||
| 947a322df7 | |||
| 0442f44049 | |||
| 43b55f1354 | |||
| 24bcd5b772 | |||
| 07c0e0e090 | |||
| dde68b8c47 | |||
| 2b918bc767 | |||
| f411cdd29a | |||
| f3b6ddec28 | |||
| 091cad4be8 | |||
| d941f7a2bd | |||
| cf91bb38cf | |||
| c487276abc | |||
| 687af3bae7 | |||
| 525db9e050 | |||
| 07d60fe334 | |||
| 3a7b1b4d6e | |||
| 3332b5ed21 | |||
| f05770656d | |||
| 61dec4c904 | |||
| e7bc8b35ee | |||
| 17a25fbe04 | |||
| 168ac5c80a | |||
| b0d6391ab7 | |||
| c74b156127 | |||
| a11e641a3a | |||
| 832292c29e | |||
| f5dedb71e1 | |||
| e7e5880c0f | |||
| 4d6a6fb033 | |||
| f86abcf932 | |||
| 42c6dadcd4 | |||
| cb49d89ba4 | |||
| f2c4f5cd07 | |||
| 3bbf743330 | |||
| edad20f8e4 | |||
| 9c5af7f9ec | |||
| 9a6022b235 | |||
| 90f26d99ab | |||
| 35ed170068 | |||
| c106bcfc54 | |||
| f4e974d2fc | |||
| 9075a96e5d | |||
| a1afcb2e7a | |||
| ed35b34461 | |||
| baa7be6a86 | |||
| acc98001f0 | |||
| e1282ad929 | |||
| e24d2a78e5 | |||
| 7cc659b2b9 | |||
| 030ed9e798 | |||
| 98c19db4c4 | |||
| 344f8adeed | |||
| 506317da3a | |||
| 1e8361252d | |||
| d437812525 | |||
| 87f24237dc | |||
| 927c441b10 | |||
| 138460e98b | |||
| c42a9a49b4 | |||
| 3f80870619 | |||
| 9c52e5ea99 | |||
| cafb171c3a | |||
| e8b9a60126 | |||
| 748117eb47 | |||
| 6c948829cd | |||
| 605d3e8d98 | |||
| ba61f2f91b | |||
| 8cd7ebf239 | |||
| eae54c38f7 | |||
| 6bfe5a229d | |||
| 937b5c4c69 | |||
| cab2c6ed9b | |||
| 808c1d1407 | |||
| 6a6da30b4d | |||
| 5413077874 | |||
| 6ffae61d70 | |||
| ab20b56894 | |||
| e2d400a136 | |||
| 0f7c44657e | |||
| 4c9c07a855 | |||
| 7e60d9f35f | |||
| 25e6630c5d | |||
| d4f2b45455 | |||
| bc270ce303 | |||
| 90667b82e8 | |||
| 09d7853909 | |||
| 179f527ae3 | |||
| e2c4522e8c | |||
| 57ec34a9cc | |||
| 7d02fe560a | |||
| 408efe43c5 | |||
| ea210f78e4 | |||
| debc37f3c2 | |||
| e642c550c4 | |||
| dfc4176c4a | |||
| 796a7c9b67 | |||
| 972146bb1e | |||
| eaba78a8b2 | |||
| e9a8d6e781 | |||
| da59e61407 | |||
| 7bdb7736b3 | |||
| dfd771cddb | |||
| 2327fedbea | |||
| 5c0c0f1ceb | |||
| 2711a4013a | |||
| 5e650c09fc | |||
| f62ab50109 | |||
| 85e6250179 | |||
| 808a369677 | |||
| 29e6d9d9eb | |||
| 604796406c | |||
| 25f240bd6f | |||
| 29c5b2b01f | |||
| dd971a0a45 | |||
| 0a1a11aae4 | |||
| a017a672bb | |||
| 0df5a25aca | |||
| 39a920802e | |||
| 23d1362718 | |||
| 58b6b184b3 | |||
| dcaa783741 | |||
| ad55a7047e | |||
| 550c3afcd8 | |||
| efcf91a9a1 | |||
| 04a30bd4df | |||
| eeb0c2f410 | |||
| 4831903987 | |||
| 991ecfec28 | |||
| f32bb17861 | |||
| 828b40dca1 | |||
| 01872fd505 | |||
| a64fdeacd8 | |||
| 1118af2a3b | |||
| 018aecdeb5 | |||
| fadc44f298 | |||
| 1987a409b8 | |||
| b93b0d6355 | |||
| e3b664447b | |||
| 6155dd23d2 | |||
| 0553a6d90c | |||
| 9223d556fb | |||
| 8e1ab8a382 | |||
| 4cb5500048 | |||
| 9cc403d680 | |||
| 8d98d2c9fd | |||
| 44160cf213 | |||
| 1b57e6b87a | |||
| 885cf9f06a | |||
| 8923fb3bb1 | |||
| e246171b56 | |||
| 1ecc0f0769 | |||
| 21573d41f7 | |||
| b553489c13 | |||
| b7fe40f96a | |||
| 3efb002aaa | |||
| 2bcef03b09 | |||
| 7a13dabb1a | |||
| 97e3be3cf6 | |||
| 7388f721e9 | |||
| 3f9247df88 | |||
| 4ad8be5c62 | |||
| a6b98fe716 | |||
| 435d91e4bb | |||
| baccabf20b | |||
| 9dcb7d057d | |||
| 77fe79ff6d | |||
| 21e0be247c | |||
| 564019efff | |||
| b5dfcb9e07 | |||
| 49681cd03d | |||
| cdcc3ab5da | |||
| 165baf349b | |||
| 5b898cb5e0 | |||
| 24e4f1d1c8 | |||
| 26e2d21925 | |||
| bc1d3f2e2a | |||
| b185b38288 | |||
| 88426ca6bf | |||
| 644ecdcaf3 | |||
| 07b01e4ff7 | |||
| 9b7293b190 | |||
| 8c515e95b7 | |||
| 06c88be18f | |||
| 8cb4579884 | |||
| b53ec2bb74 | |||
| a9883c6ee7 | |||
| 1446f3f89c | |||
| 709cf42e06 | |||
| 0d493433d3 | |||
| 318fd402e1 | |||
| 1671a9f7fd | |||
| 72f0984c4e | |||
| 7da687624c | |||
| 4ecc15449c | |||
| b05f3cdc4b | |||
| 4c627b6a57 | |||
| d0770f8154 | |||
| 25d5103a4c | |||
| 98ab2d0a44 | |||
| d57c35472a | |||
| ed88c64a5c | |||
| f1d125b08e | |||
| 9d84af2a52 | |||
| 1c52897f85 | |||
| 0a9117bd61 | |||
| abe0e8fbad | |||
| a05eb5558e | |||
| 2bf03fc2e0 | |||
| 17aaad9033 | |||
| 22f75de983 | |||
| a825339917 | |||
| 0e91160efe | |||
| 6b1ce97894 | |||
| 38ce5978dd | |||
| 5b4c20944d | |||
| 12c256a78b | |||
| 6b45969dc6 | |||
| 4abab60161 | |||
| 5bdbf3185d | |||
| 159f20a93e | |||
| 24335e6c2b | |||
| fe4763e560 | |||
| 8a80d01b70 | |||
| ee21767d92 | |||
| d1e488ab9b | |||
| 5863a07a46 | |||
| 50a8ba5661 | |||
| 673922a628 | |||
| 3282dda765 | |||
| 5da126bc67 | |||
| b503eca0d4 | |||
| 95e75e70b5 | |||
| d86ad34353 | |||
| c0b2cf55e4 | |||
| bdb8dec713 | |||
| d4fc00666b | |||
| 248f5a1efc | |||
| 036e58c5b5 | |||
| ba7cf5ad88 | |||
| 3f8bd31bf3 | |||
| de8c665fe3 | |||
| 23cd28f38c | |||
| 00c706dc8a | |||
| 93973e0472 | |||
| 3e46ac327a | |||
| a9afba6a38 | |||
| 95764ed91d | |||
| b1dbc661bb | |||
| 809901f7c8 | |||
| f8c209cf25 | |||
| 564ad754a8 | |||
| d940ccbe0b | |||
| cbf62420e0 | |||
| 066673fb37 | |||
| 0bec5987c8 | |||
| 7c5c76bcdf | |||
| 4ef107fccb | |||
| 9addd44bb4 | |||
| 0bc2276751 | |||
| 3612fa390d | |||
| 76e06c15c6 | |||
| ba22230a4f | |||
| cf77a2d72d | |||
| e8fd0db959 | |||
| 99c7dc1929 | |||
| ceba417524 | |||
| 4e5cd5956d | |||
| c116d32f39 | |||
| e2338e14a7 | |||
| 057a4fc85b | |||
| dfcad939ff | |||
| db6be2a885 | |||
| 5dfaf89ba2 | |||
| 0d72003e6e | |||
| 47b4f21994 | |||
| 8e6e56c066 | |||
| f3ad037584 | |||
| 13898c79db | |||
| 323801ad4b | |||
| c013bb2cbc | |||
| 8789bc4fa6 | |||
| f02688e713 | |||
| 5eefd56230 | |||
| 51ee36d039 | |||
| db65a2ca7e | |||
| 6eef6e3ab9 | |||
| 3dbced7596 | |||
| 86bef7c3b5 | |||
| 2436022f22 | |||
| 7fbf87df43 | |||
| ce2e9f1cfb | |||
| 5bf54816e8 | |||
| 560b5b0efc | |||
| b211eb7afe | |||
| fc6d1fe719 | |||
| 93f39f3db6 | |||
| c47c19634e | |||
| 5806a1f251 | |||
| 803d87c60f | |||
| ff27e31a89 | |||
| 949282574a | |||
| c42c551cff | |||
| d3507a690a | |||
| 36a5774a81 | |||
| ace9ed3fc8 | |||
| 9042827e3f | |||
| 8b7eb8620a | |||
| 8223305818 | |||
| eaaaeface3 |
@@ -710,7 +710,7 @@ if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('cliadminemail', 'install'));
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['adminemail']);
|
||||
$options['adminemail'] = cli_input($prompt);
|
||||
$options['adminemail'] = cli_input($prompt, $options['adminemail']);
|
||||
}
|
||||
|
||||
// Validate that the address provided was an e-mail address.
|
||||
|
||||
+1
-2
@@ -823,8 +823,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
*/
|
||||
protected function registration_warning($registered) {
|
||||
|
||||
if (!$registered) {
|
||||
|
||||
if (!$registered && site_is_public()) {
|
||||
if (has_capability('moodle/site:config', context_system::instance())) {
|
||||
$registerbutton = $this->single_button(new moodle_url('/admin/registration/index.php'),
|
||||
get_string('register', 'admin'));
|
||||
|
||||
@@ -69,8 +69,11 @@ class core_role_check_users_selector extends user_selector_base {
|
||||
|
||||
if ($coursecontext and $coursecontext != SITEID) {
|
||||
$sql1 = " FROM {user} u
|
||||
JOIN {user_enrolments} ue ON (ue.userid = u.id)
|
||||
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid1)
|
||||
JOIN (SELECT DISTINCT subu.id
|
||||
FROM {user} subu
|
||||
JOIN {user_enrolments} ue ON (ue.userid = subu.id)
|
||||
JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid1)
|
||||
) subq ON subq.id = u.id
|
||||
WHERE $wherecondition";
|
||||
$params['courseid1'] = $coursecontext->instanceid;
|
||||
|
||||
|
||||
@@ -330,7 +330,7 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_skip_modif_prev', new lang_string('skipmodifprev', 'backup'), new lang_string('skipmodifprevhelp', 'backup'), 0));
|
||||
|
||||
// Automated defaults section.
|
||||
$temp->add(new admin_setting_heading('automatedsettings', new lang_string('automatedsettings','backup'), ''));
|
||||
$temp->add(new admin_setting_heading('automatedsettings', new lang_string('automatedsettings','backup'), new lang_string('recyclebin_desc', 'backup')));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_users', new lang_string('generalusers', 'backup'), new lang_string('configgeneralusers', 'backup'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_role_assignments', new lang_string('generalroleassignments','backup'), new lang_string('configgeneralroleassignments','backup'), 1));
|
||||
$temp->add(new admin_setting_configcheckbox('backup/backup_auto_activities', new lang_string('generalactivities','backup'), new lang_string('configgeneralactivities','backup'), 1));
|
||||
|
||||
@@ -58,7 +58,7 @@ class behat_admin extends behat_base {
|
||||
// We expect admin block to be visible, otherwise go to homepage.
|
||||
if (!$this->getSession()->getPage()->find('css', '.block_settings')) {
|
||||
$this->getSession()->visit($this->locate_path('/'));
|
||||
$this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
|
||||
$this->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
|
||||
}
|
||||
|
||||
// Search by label.
|
||||
@@ -67,7 +67,7 @@ class behat_admin extends behat_base {
|
||||
$submitsearch = $this->find('css', 'form.adminsearchform input[type=submit]');
|
||||
$submitsearch->press();
|
||||
|
||||
$this->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
|
||||
$this->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
|
||||
|
||||
// Admin settings does not use the same DOM structure than other moodle forms
|
||||
// but we also need to use lib/behat/form_field/* to deal with the different moodle form elements.
|
||||
|
||||
@@ -1,57 +1,74 @@
|
||||
@core @core_admin
|
||||
Feature: Enable multiple accounts to have the same email address
|
||||
In order to have multiple accounts registerd on the system with the same email address
|
||||
@core @core_admin @core_user
|
||||
Feature: Allowing multiple accounts to have the same email address
|
||||
In order to manage user accounts
|
||||
As an admin
|
||||
I need to enable multiple accounts to be registered with the same email address and verify it is applied
|
||||
I need to be able to set whether to allow multiple accounts with the same email or not
|
||||
|
||||
Background:
|
||||
Given I log in as "admin"
|
||||
|
||||
Scenario: Enable registration of multiple accounts with the same email address
|
||||
Scenario Outline: Create a user with the same email as an existing user
|
||||
Given the following config values are set as admin:
|
||||
| allowaccountssameemail | 1 |
|
||||
When I navigate to "Users > Accounts > Add a new user" in site administration
|
||||
| allowaccountssameemail | <allowsameemail> |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| s1 | John | Doe | s1@example.com |
|
||||
When I log in as "admin"
|
||||
And I navigate to "Users > Accounts > Add a new user" in site administration
|
||||
And I set the following fields to these values:
|
||||
| Username | testmultiemailuser1 |
|
||||
| Choose an authentication method | Manual accounts |
|
||||
| New password | test@User1 |
|
||||
| First name | Test |
|
||||
| Surname | Multi1 |
|
||||
| Email address | testmultiemailuser@example.com |
|
||||
| Username | s2 |
|
||||
| First name | Jane |
|
||||
| Surname | Doe |
|
||||
| Email address | <email> |
|
||||
| New password | test |
|
||||
And I press "Create user"
|
||||
And I should see "Test Multi1"
|
||||
And I press "Add a new user"
|
||||
And I set the following fields to these values:
|
||||
| Username | testmultiemailuser2 |
|
||||
| Choose an authentication method | Manual accounts |
|
||||
| New password | test@User2 |
|
||||
| First name | Test |
|
||||
| Surname | Multi2 |
|
||||
| Email address | testmultiemailuser@example.com |
|
||||
And I press "Create user"
|
||||
Then I should see "Test Multi2"
|
||||
And I should not see "This email address is already registered"
|
||||
Then I should <expect> "This email address is already registered."
|
||||
|
||||
Scenario: Disable registration of multiple accounts with the same email address
|
||||
Examples:
|
||||
| allowsameemail | email | expect |
|
||||
| 0 | s1@example.com | see |
|
||||
| 0 | S1@EXAMPLE.COM | see |
|
||||
| 1 | s1@example.com | not see |
|
||||
| 1 | S1@EXAMPLE.COM | not see |
|
||||
|
||||
Scenario Outline: Update a user with the same email as an existing user
|
||||
Given the following config values are set as admin:
|
||||
| allowaccountssameemail | 0 |
|
||||
When I navigate to "Users > Accounts > Add a new user" in site administration
|
||||
And I set the following fields to these values:
|
||||
| Username | testmultiemailuser1 |
|
||||
| Choose an authentication method | Manual accounts |
|
||||
| New password | test@User1 |
|
||||
| First name | Test |
|
||||
| Surname | Multi1 |
|
||||
| Email address | testmultiemailuser@example.com |
|
||||
And I press "Create user"
|
||||
And I should see "Test Multi1"
|
||||
And I press "Add a new user"
|
||||
And I set the following fields to these values:
|
||||
| Username | testmultiemailuser2 |
|
||||
| Choose an authentication method | Manual accounts |
|
||||
| New password | test@User2 |
|
||||
| First name | Test |
|
||||
| Surname | Multi2 |
|
||||
| Email address | testmultiemailuser@example.com |
|
||||
And I press "Create user"
|
||||
Then I should see "This email address is already registered"
|
||||
| allowaccountssameemail | <allowsameemail> |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| s1 | John | Doe | s1@example.com |
|
||||
| s2 | Jane | Doe | s2@example.com |
|
||||
When I log in as "admin"
|
||||
And I navigate to "Users > Accounts > Browse list of users" in site administration
|
||||
And I click on "Edit" "link" in the "Jane Doe" "table_row"
|
||||
And I set the field "Email address" to "<email>"
|
||||
And I press "Update profile"
|
||||
Then I should <expect> "This email address is already registered."
|
||||
|
||||
Examples:
|
||||
| allowsameemail | email | expect |
|
||||
| 0 | s1@example.com | see |
|
||||
| 0 | S1@EXAMPLE.COM | see |
|
||||
| 1 | s1@example.com | not see |
|
||||
| 1 | S1@EXAMPLE.COM | not see |
|
||||
| 0 | S2@EXAMPLE.COM | not see |
|
||||
| 1 | S2@EXAMPLE.COM | not see |
|
||||
|
||||
Scenario Outline: Update own user profile with the same email as an existing user
|
||||
Given the following config values are set as admin:
|
||||
| allowaccountssameemail | <allowsameemail> |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| s1 | John | Doe | s1@example.com |
|
||||
| s2 | Jane | Doe | s2@example.com |
|
||||
When I log in as "s2"
|
||||
And I open my profile in edit mode
|
||||
And I set the field "Email address" to "<email>"
|
||||
And I press "Update profile"
|
||||
Then I should <expect> "This email address is already registered."
|
||||
|
||||
Examples:
|
||||
| allowsameemail | email | expect |
|
||||
| 0 | s1@example.com | see |
|
||||
| 0 | S1@EXAMPLE.COM | see |
|
||||
| 1 | s1@example.com | not see |
|
||||
| 1 | S1@EXAMPLE.COM | not see |
|
||||
| 0 | S2@EXAMPLE.COM | not see |
|
||||
| 1 | S2@EXAMPLE.COM | not see |
|
||||
|
||||
@@ -162,11 +162,11 @@ class tool_behat_renderer extends plugin_renderer_base {
|
||||
$html .= $this->output->heading($title);
|
||||
|
||||
// Info.
|
||||
$installurl = behat_command::DOCS_URL . '#Installation';
|
||||
$installurl = behat_command::DOCS_URL;
|
||||
$installlink = html_writer::tag('a', $installurl, array('href' => $installurl, 'target' => '_blank'));
|
||||
$writetestsurl = behat_command::DOCS_URL . '#Writing_features';
|
||||
$writetestsurl = 'https://docs.moodle.org/dev/Writing acceptance tests';
|
||||
$writetestslink = html_writer::tag('a', $writetestsurl, array('href' => $writetestsurl, 'target' => '_blank'));
|
||||
$writestepsurl = behat_command::DOCS_URL . '#Adding_steps_definitions';
|
||||
$writestepsurl = 'https://docs.moodle.org/dev/Writing_new_acceptance_test_step_definitions';
|
||||
$writestepslink = html_writer::tag('a', $writestepsurl, array('href' => $writestepsurl, 'target' => '_blank'));
|
||||
$infos = array(
|
||||
get_string('installinfo', 'tool_behat', $installlink),
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
}} data-status="{{status}}"{{!
|
||||
}}>
|
||||
<td>{{typename}}</td>
|
||||
<td>{{#userdate}} {{timecreated}}, {{#str}} strftimedatetime {{/str}} {{/userdate}}</td>
|
||||
<td>{{#userdate}} {{timecreated}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}</td>
|
||||
<td><a href="{{requestedbyuser.profileurl}}" title="{{#str}}viewprofile{{/str}}">{{requestedbyuser.fullname}}</a></td>
|
||||
<td>
|
||||
<span class="label {{statuslabelclass}}" title="{{statuslabeltitle}}">{{statuslabel}}</span>
|
||||
|
||||
@@ -67,7 +67,8 @@
|
||||
<a href="mailto:{{foruser.email}}">{{foruser.email}}</a>
|
||||
<div class="clearfix m-t-1 m-b-1">
|
||||
<span class="pull-left m-r-1">
|
||||
<strong>{{#str}}daterequesteddetail, tool_dataprivacy{{/str}}</strong> {{#userdate}} {{timecreated}}, {{#str}} strftimedatetime {{/str}} {{/userdate}}
|
||||
<strong>{{#str}}daterequesteddetail, tool_dataprivacy{{/str}}</strong>
|
||||
{{#userdate}} {{timecreated}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}
|
||||
</span>
|
||||
<span class="pull-left m-r-1">
|
||||
<strong>{{#str}}statusdetail, tool_dataprivacy{{/str}}</strong>
|
||||
|
||||
@@ -137,14 +137,14 @@ abstract class restore_tool_log_logstore_subplugin extends restore_subplugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now we want to serialize it so we can store it in the DB.
|
||||
$data->other = serialize($data->other);
|
||||
} else {
|
||||
$message = "Event class not found: \"$eventclass\". Skipping log record.";
|
||||
$this->log($message, backup::LOG_DEBUG);
|
||||
return; // No such class, can not restore.
|
||||
}
|
||||
}
|
||||
// Now we want to serialize it so we can store it in the DB.
|
||||
$data->other = serialize($data->other);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -422,6 +422,7 @@ class external extends external_api {
|
||||
'comppath' => competency_path_exporter::get_read_structure(),
|
||||
))),
|
||||
'manageurl' => new external_value(PARAM_LOCALURL, 'Url to the manage competencies page.'),
|
||||
'pluginbaseurl' => new external_value(PARAM_LOCALURL, 'Url to the course competencies page.'),
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ class course_competencies_page implements renderable, templatable {
|
||||
$data->courseid = $this->courseid;
|
||||
$data->pagecontextid = $this->context->id;
|
||||
$data->competencies = array();
|
||||
$data->pluginbaseurl = (new moodle_url('/admin/tool/lp'))->out(true);
|
||||
|
||||
$gradable = is_enrolled($this->context, $USER, 'moodle/competency:coursecompetencygradable');
|
||||
if ($gradable) {
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<div class="clearfix"></div>
|
||||
{{/canmanagecoursecompetencies}}
|
||||
{{#competency}}
|
||||
<a href="{{pluginbaseurl}}user_competency_in_course.php?courseid={{courseid}}&competencyid={{competency.id}}&userid={{gradableuserid}}"
|
||||
<a href="{{pluginbaseurl}}/user_competency_in_course.php?courseid={{courseid}}&competencyid={{competency.id}}&userid={{gradableuserid}}"
|
||||
id="competency-info-link-{{competency.id}}"
|
||||
title="{{#str}}viewdetails, tool_lp{{/str}}">
|
||||
<p><strong>{{{competency.shortname}}} <em>{{competency.idnumber}}</em></strong></p>
|
||||
|
||||
@@ -672,7 +672,7 @@ class manager {
|
||||
$attachment->charset = $partdata->getCharset();
|
||||
$attachment->description = $partdata->getDescription();
|
||||
$attachment->contentid = $partdata->getContentId();
|
||||
$attachment->filesize = $messagedata->getBodyPartSize($part);
|
||||
$attachment->filesize = $partdata->getBytes();
|
||||
|
||||
if (!empty($CFG->antiviruses)) {
|
||||
mtrace("--> Attempting virus scan of '{$attachment->filename}'");
|
||||
|
||||
@@ -99,7 +99,7 @@ class api {
|
||||
$lang = array();
|
||||
if (!empty($addoninfo['lang'])) {
|
||||
$stringmanager = get_string_manager();
|
||||
$langs = $stringmanager->get_list_of_translations();
|
||||
$langs = $stringmanager->get_list_of_translations(true);
|
||||
foreach ($langs as $langid => $langname) {
|
||||
foreach ($addoninfo['lang'] as $stringinfo) {
|
||||
$lang[$langid][$stringinfo[0]] =
|
||||
|
||||
@@ -66,6 +66,12 @@ function tool_mobile_create_app_download_url() {
|
||||
}
|
||||
|
||||
$downloadurl = new moodle_url($mobilesettings->setuplink);
|
||||
|
||||
// Do not update the URL if it is a custom one (we may break it completely).
|
||||
if ($mobilesettings->setuplink != 'https://download.moodle.org/mobile') {
|
||||
return $downloadurl;
|
||||
}
|
||||
|
||||
$downloadurl->param('version', $CFG->version);
|
||||
$downloadurl->param('lang', current_language());
|
||||
|
||||
|
||||
@@ -301,6 +301,8 @@ class page_agreedocs implements renderable, templatable {
|
||||
redirect(new moodle_url('/admin/tool/policy/view.php', $urlparams));
|
||||
}
|
||||
} else {
|
||||
// Update the policyagreed for the user to avoid infinite loop because there are no policies to-be-accepted.
|
||||
api::update_policyagreed($userid);
|
||||
$this->redirect_to_previous_url();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
|
||||
And I press "Next"
|
||||
And I set the field "I agree to the This site policy" to "1"
|
||||
And I press "Next"
|
||||
And I should not see "Next"
|
||||
And I should see "Course overview"
|
||||
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
|
||||
And I click on "1 of 4 (25%)" "link" in the "This site policy" "table_row"
|
||||
And I click on "Accept This site policy" "link" in the "User One" "table_row"
|
||||
@@ -292,3 +292,23 @@ Feature: Viewing acceptances reports and accepting on behalf of other users
|
||||
When I press "Give consent"
|
||||
Then "Accepted on user's behalf" "text" should exist in the "User One" "table_row"
|
||||
And "Accepted on user's behalf" "text" should exist in the "User Two" "table_row"
|
||||
|
||||
Scenario: View acceptances made by users on their own after inactivating a policy
|
||||
Given I log in as "user1"
|
||||
And I should see "This site policy"
|
||||
And I should not see "Course overview"
|
||||
And I press "Next"
|
||||
And I set the field "I agree to the This site policy" to "1"
|
||||
And I press "Next"
|
||||
And I should see "Course overview"
|
||||
And I log out
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Privacy and policies > Manage policies" in site administration
|
||||
And I click on "Actions" "link_or_button" in the "This privacy policy" "table_row"
|
||||
And I click on "Set status to \"Active\"" "link" in the "This privacy policy" "table_row"
|
||||
And I press "Continue"
|
||||
And I click on "Set status to \"Inactive\"" "link" in the "This privacy policy" "table_row"
|
||||
And I press "Continue"
|
||||
And I log out
|
||||
When I log in as "user1"
|
||||
Then I should see "Course overview"
|
||||
|
||||
@@ -108,6 +108,16 @@ class category_bin extends base_bin {
|
||||
|
||||
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
|
||||
// As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage
|
||||
// setting (storing backups @ real location. For recycle bin we want to ensure that
|
||||
// backup files are always stored in Moodle file area. In order to achieve that, we
|
||||
// hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other
|
||||
// operations. See MDL-65218 for more information.
|
||||
// This hack will be removed once recycle bin switches to use its own backup mode, with
|
||||
// own preferences and 100% appart from MODLE_AUTOMATED.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
$CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0];
|
||||
|
||||
// Backup the course.
|
||||
$user = get_admin();
|
||||
$controller = new \backup_controller(
|
||||
@@ -115,11 +125,15 @@ class category_bin extends base_bin {
|
||||
$course->id,
|
||||
\backup::FORMAT_MOODLE,
|
||||
\backup::INTERACTIVE_NO,
|
||||
\backup::MODE_GENERAL,
|
||||
\backup::MODE_AUTOMATED,
|
||||
$user->id
|
||||
);
|
||||
$controller->execute_plan();
|
||||
|
||||
// We don't need the forced setting anymore, hence unsetting it.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
unset($CFG->forced_plugin_settings['backup']);
|
||||
|
||||
// Grab the result.
|
||||
$result = $controller->get_results();
|
||||
if (!isset($result['backup_destination'])) {
|
||||
@@ -233,7 +247,7 @@ class category_bin extends base_bin {
|
||||
$tempdir,
|
||||
$course->id,
|
||||
\backup::INTERACTIVE_NO,
|
||||
\backup::MODE_GENERAL,
|
||||
\backup::MODE_AUTOMATED,
|
||||
$user->id,
|
||||
\backup::TARGET_NEW_COURSE
|
||||
);
|
||||
|
||||
@@ -112,6 +112,16 @@ class course_bin extends base_bin {
|
||||
return;
|
||||
}
|
||||
|
||||
// As far as recycle bin is using MODE_AUTOMATED, it observes the backup_auto_storage
|
||||
// setting (storing backups @ real location. For recycle bin we want to ensure that
|
||||
// backup files are always stored in Moodle file area. In order to achieve that, we
|
||||
// hack the setting here via $CFG->forced_plugin_settings, so it won't interfere other
|
||||
// operations. See MDL-65218 for more information.
|
||||
// This hack will be removed once recycle bin switches to use its own backup mode, with
|
||||
// own preferences and 100% appart from MODLE_AUTOMATED.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
$CFG->forced_plugin_settings['backup'] = ['backup_auto_storage' => 0];
|
||||
|
||||
// Backup the activity.
|
||||
$user = get_admin();
|
||||
$controller = new \backup_controller(
|
||||
@@ -119,11 +129,15 @@ class course_bin extends base_bin {
|
||||
$cm->id,
|
||||
\backup::FORMAT_MOODLE,
|
||||
\backup::INTERACTIVE_NO,
|
||||
\backup::MODE_GENERAL,
|
||||
\backup::MODE_AUTOMATED,
|
||||
$user->id
|
||||
);
|
||||
$controller->execute_plan();
|
||||
|
||||
// We don't need the forced setting anymore, hence unsetting it.
|
||||
// TODO: Remove this as part of MDL-65228.
|
||||
unset($CFG->forced_plugin_settings['backup']);
|
||||
|
||||
// Grab the result.
|
||||
$result = $controller->get_results();
|
||||
if (!isset($result['backup_destination'])) {
|
||||
@@ -224,7 +238,7 @@ class course_bin extends base_bin {
|
||||
$tempdir,
|
||||
$this->_courseid,
|
||||
\backup::INTERACTIVE_NO,
|
||||
\backup::MODE_GENERAL,
|
||||
\backup::MODE_AUTOMATED,
|
||||
$user->id,
|
||||
\backup::TARGET_EXISTING_ADDING
|
||||
);
|
||||
|
||||
@@ -176,4 +176,116 @@ class tool_recyclebin_category_bin_tests extends advanced_testcase {
|
||||
$course = reset($courses);
|
||||
$this->assertEquals('Test course 2', $course->fullname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for test_course_restore_with_userdata() and test_course_restore_without_userdata()
|
||||
*
|
||||
* Used to verify that recycle bin is immune to various settings. Provides plugin, name, value for
|
||||
* direct usage with set_config()
|
||||
*/
|
||||
public function recycle_bin_settings_provider() {
|
||||
return [
|
||||
'backup/backup_auto_storage moodle' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 0],
|
||||
]],
|
||||
|
||||
'backup/backup_auto_storage external' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 1],
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
|
||||
]],
|
||||
|
||||
'backup/backup_auto_storage mixed' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 2],
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user data is restored when course is restored.
|
||||
*
|
||||
* @dataProvider recycle_bin_settings_provider
|
||||
* @param array $settings array of plugin, name, value stdClass().
|
||||
*/
|
||||
public function test_course_restore_with_userdata($settings) {
|
||||
global $DB;
|
||||
|
||||
// Force configuration changes from provider.
|
||||
foreach ($settings as $setting) {
|
||||
// Need to create a directory for backup_auto_destination.
|
||||
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
|
||||
$setting->value = make_request_directory();
|
||||
}
|
||||
set_config($setting->name, $setting->value, $setting->plugin);
|
||||
}
|
||||
|
||||
// We want user data to be included for this test.
|
||||
set_config('backup_auto_users', true, 'backup');
|
||||
|
||||
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
|
||||
|
||||
// Delete course.
|
||||
delete_course($this->course, false);
|
||||
$this->assertFalse($DB->record_exists('course', ['id' => $this->course->id]));
|
||||
|
||||
// Verify there is now a backup @ cat recycle bin file area.
|
||||
$recyclebin = new \tool_recyclebin\category_bin($this->course->category);
|
||||
$this->assertEquals(1, count($recyclebin->get_items()));
|
||||
|
||||
// Restore the recycle bin item.
|
||||
$recyclebin->restore_item(current($recyclebin->get_items()));
|
||||
|
||||
// Get the new course.
|
||||
$newcourse = $DB->get_record('course', ['shortname' => $this->course->shortname], '*', MUST_EXIST);
|
||||
|
||||
// Check that it was removed from the recycle bin.
|
||||
$this->assertEquals(0, count($recyclebin->get_items()));
|
||||
|
||||
// Verify that student DOES continue enrolled.
|
||||
$this->assertTrue(is_enrolled(context_course::instance($newcourse->id), $student->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user data is not restored when course is restored.
|
||||
*
|
||||
* @dataProvider recycle_bin_settings_provider
|
||||
* @param array $settings array of plugin, name, value stdClass().
|
||||
*/
|
||||
public function test_course_restore_without_userdata($settings) {
|
||||
global $DB;
|
||||
|
||||
// Force configuration changes from provider.
|
||||
foreach ($settings as $setting) {
|
||||
// Need to create a directory for backup_auto_destination.
|
||||
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
|
||||
$setting->value = make_request_directory();
|
||||
}
|
||||
set_config($setting->name, $setting->value, $setting->plugin);
|
||||
}
|
||||
|
||||
// We want user data to be included for this test.
|
||||
set_config('backup_auto_users', false, 'backup');
|
||||
|
||||
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
|
||||
|
||||
// Delete course.
|
||||
delete_course($this->course, false);
|
||||
$this->assertFalse($DB->record_exists('course', ['id' => $this->course->id]));
|
||||
|
||||
// Verify there is now a backup @ cat recycle bin file area.
|
||||
$recyclebin = new \tool_recyclebin\category_bin($this->course->category);
|
||||
$this->assertEquals(1, count($recyclebin->get_items()));
|
||||
|
||||
// Restore the recycle bin item.
|
||||
$recyclebin->restore_item(current($recyclebin->get_items()));
|
||||
|
||||
// Get the new course.
|
||||
$newcourse = $DB->get_record('course', ['shortname' => $this->course->shortname], '*', MUST_EXIST);
|
||||
|
||||
// Check that it was removed from the recycle bin.
|
||||
$this->assertEquals(0, count($recyclebin->get_items()));
|
||||
|
||||
// Verify that student DOES NOT continue enrolled.
|
||||
$this->assertFalse(is_enrolled(context_course::instance($newcourse->id), $student->id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
|
||||
|
||||
$this->course = $this->getDataGenerator()->create_course();
|
||||
$this->quiz = $this->getDataGenerator()->get_plugin_generator('mod_quiz')->create_instance(array(
|
||||
'course' => $this->course->id
|
||||
'course' => $this->course->id, 'grade' => 100.0, 'sumgrades' => 1
|
||||
));
|
||||
}
|
||||
|
||||
@@ -173,4 +173,147 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
|
||||
$deletedbook = reset($items);
|
||||
$this->assertEquals($book->name, $deletedbook->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for test_coursemodule_restore_with_userdata() and test_coursemodule_restore_without_userdata()
|
||||
*
|
||||
* Used to verify that recycle bin is immune to various settings. Provides plugin, name, value for
|
||||
* direct usage with set_config()
|
||||
*/
|
||||
public function recycle_bin_settings_provider() {
|
||||
return [
|
||||
'backup/backup_auto_storage moodle' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 0],
|
||||
]],
|
||||
|
||||
'backup/backup_auto_storage external' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 1],
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
|
||||
]],
|
||||
|
||||
'backup/backup_auto_storage mixed' => [[
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_storage', 'value' => 2],
|
||||
(object)['plugin' => 'backup', 'name' => 'backup_auto_destination', 'value' => true],
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user data is restored when module is restored.
|
||||
*
|
||||
* @dataProvider recycle_bin_settings_provider
|
||||
* @param array $settings array of plugin, name, value stdClass().
|
||||
*/
|
||||
public function test_coursemodule_restore_with_userdata($settings) {
|
||||
// Force configuration changes from provider.
|
||||
foreach ($settings as $setting) {
|
||||
// Need to create a directory for backup_auto_destination.
|
||||
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
|
||||
$setting->value = make_request_directory();
|
||||
}
|
||||
set_config($setting->name, $setting->value, $setting->plugin);
|
||||
}
|
||||
|
||||
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
|
||||
$this->setUser($student);
|
||||
|
||||
set_config('backup_auto_users', true, 'backup');
|
||||
$this->create_quiz_attempt($this->quiz, $student);
|
||||
|
||||
// Delete quiz.
|
||||
$cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
|
||||
course_delete_module($cm->id);
|
||||
phpunit_util::run_all_adhoc_tasks();
|
||||
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
|
||||
$this->assertEquals(0, count($quizzes));
|
||||
|
||||
// Restore quiz.
|
||||
$recyclebin = new \tool_recyclebin\course_bin($this->course->id);
|
||||
foreach ($recyclebin->get_items() as $item) {
|
||||
$recyclebin->restore_item($item);
|
||||
}
|
||||
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
|
||||
$this->assertEquals(1, count($quizzes));
|
||||
$cm = array_pop($quizzes);
|
||||
|
||||
// Check if user quiz attempt data is restored.
|
||||
$attempts = quiz_get_user_attempts($cm->instance, $student->id);
|
||||
$this->assertEquals(1, count($attempts));
|
||||
$attempt = array_pop($attempts);
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$this->assertEquals($student->id, $attemptobj->get_userid());
|
||||
$this->assertEquals(true, $attemptobj->is_finished());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that user data is not restored when module is restored.
|
||||
*
|
||||
* @dataProvider recycle_bin_settings_provider
|
||||
* @param array $settings array of plugin, name, value stdClass().
|
||||
*/
|
||||
public function test_coursemodule_restore_without_userdata($settings) {
|
||||
// Force configuration changes from provider.
|
||||
foreach ($settings as $setting) {
|
||||
// Need to create a directory for backup_auto_destination.
|
||||
if ($setting->plugin === 'backup' && $setting->name === 'backup_auto_destination' && $setting->value === true) {
|
||||
$setting->value = make_request_directory();
|
||||
}
|
||||
set_config($setting->name, $setting->value, $setting->plugin);
|
||||
}
|
||||
|
||||
$student = $this->getDataGenerator()->create_and_enrol($this->course, 'student');
|
||||
$this->setUser($student);
|
||||
|
||||
set_config('backup_auto_users', false, 'backup');
|
||||
$this->create_quiz_attempt($this->quiz, $student);
|
||||
|
||||
// Delete quiz.
|
||||
$cm = get_coursemodule_from_instance('quiz', $this->quiz->id);
|
||||
course_delete_module($cm->id);
|
||||
phpunit_util::run_all_adhoc_tasks();
|
||||
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
|
||||
$this->assertEquals(0, count($quizzes));
|
||||
|
||||
// Restore quiz.
|
||||
$recyclebin = new \tool_recyclebin\course_bin($this->course->id);
|
||||
foreach ($recyclebin->get_items() as $item) {
|
||||
$recyclebin->restore_item($item);
|
||||
}
|
||||
$quizzes = get_coursemodules_in_course('quiz', $this->course->id);
|
||||
$this->assertEquals(1, count($quizzes));
|
||||
$cm = array_pop($quizzes);
|
||||
|
||||
// Check if user quiz attempt data is restored.
|
||||
$attempts = quiz_get_user_attempts($cm->instance, $student->id);
|
||||
$this->assertEquals(0, count($attempts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a question to quiz and create a quiz attempt.
|
||||
* @param \stdClass $quiz Quiz
|
||||
* @param \stdClass $student User
|
||||
* @throws coding_exception
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
private function create_quiz_attempt($quiz, $student) {
|
||||
// Add Question.
|
||||
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
|
||||
$cat = $questiongenerator->create_question_category();
|
||||
$numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
|
||||
quiz_add_quiz_question($numq->id, $quiz);
|
||||
|
||||
// Create quiz attempt.
|
||||
$quizobj = quiz::create($quiz->id, $student->id);
|
||||
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
|
||||
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
|
||||
$timenow = time();
|
||||
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $student->id);
|
||||
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
|
||||
quiz_attempt_save_started($quizobj, $quba, $attempt);
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$tosubmit = array(1 => array('answer' => '0'));
|
||||
$attemptobj->process_submitted_actions($timenow, false, $tosubmit);
|
||||
$attemptobj = quiz_attempt::create($attempt->id);
|
||||
$attemptobj->process_finish($timenow, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +183,7 @@ function uu_validate_user_upload_columns(csv_import_reader $cir, $stdfields, $pr
|
||||
$processed = array();
|
||||
foreach ($columns as $key=>$unused) {
|
||||
$field = $columns[$key];
|
||||
$field = trim($field);
|
||||
$lcfield = core_text::strtolower($field);
|
||||
if (in_array($field, $stdfields) or in_array($lcfield, $stdfields)) {
|
||||
// standard fields are only lowercase
|
||||
|
||||
@@ -796,8 +796,7 @@ class manager {
|
||||
// the format filename => version. The version value needs to
|
||||
// be increased if the tour has been updated.
|
||||
$shippedtours = [
|
||||
'36_dashboard.json' => 3,
|
||||
'36_messaging.json' => 3,
|
||||
'36_dashboard.json' => 3
|
||||
];
|
||||
|
||||
// These are tours that we used to ship but don't ship any longer.
|
||||
@@ -807,6 +806,12 @@ class manager {
|
||||
'boost_course_view.json' => 1,
|
||||
];
|
||||
|
||||
if ($CFG->messaging) {
|
||||
$shippedtours['36_messaging.json'] = 3;
|
||||
} else {
|
||||
$unshippedtours['36_messaging.json'] = 3;
|
||||
}
|
||||
|
||||
$existingtourrecords = $DB->get_recordset('tool_usertours_tours');
|
||||
|
||||
// Get all of the existing shipped tours and check if they need to be
|
||||
|
||||
@@ -57,5 +57,12 @@ function xmldb_tool_usertours_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.6.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2018120301) {
|
||||
// Update the tours shipped with Moodle.
|
||||
manager::update_shipped_tours();
|
||||
|
||||
upgrade_plugin_savepoint(true, 2018120301, 'tool', 'usertours');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2018120300; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->version = 2018120301; // The current module version (Date: YYYYMMDDXX).
|
||||
$plugin->requires = 2018112800; // Requires this Moodle version.
|
||||
$plugin->component = 'tool_usertours'; // Full name of the plugin (used for diagnostics).
|
||||
|
||||
@@ -577,9 +577,14 @@ abstract class base {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Remove samples the target consider invalid.
|
||||
$this->analysabletarget->add_sample_data($samplesdata);
|
||||
$this->analysabletarget->filter_out_invalid_samples($sampleids, $analysable, $includetarget);
|
||||
try {
|
||||
// Remove samples the target consider invalid.
|
||||
$this->analysabletarget->add_sample_data($samplesdata);
|
||||
$this->analysabletarget->filter_out_invalid_samples($sampleids, $analysable, $includetarget);
|
||||
} catch (\Throwable $e) {
|
||||
$dataset->close_process();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (!$sampleids) {
|
||||
$result->status = \core_analytics\model::NO_DATASET;
|
||||
@@ -588,19 +593,24 @@ abstract class base {
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($this->indicators as $key => $indicator) {
|
||||
// The analyser attaches the main entities the sample depends on and are provided to the
|
||||
// indicator to calculate the sample.
|
||||
$this->indicators[$key]->add_sample_data($samplesdata);
|
||||
}
|
||||
try {
|
||||
foreach ($this->indicators as $key => $indicator) {
|
||||
// The analyser attaches the main entities the sample depends on and are provided to the
|
||||
// indicator to calculate the sample.
|
||||
$this->indicators[$key]->add_sample_data($samplesdata);
|
||||
}
|
||||
|
||||
// Here we start the memory intensive process that will last until $data var is
|
||||
// unset (until the method is finished basically).
|
||||
if ($includetarget) {
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges,
|
||||
$this->analysabletarget);
|
||||
} else {
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges);
|
||||
// Here we start the memory intensive process that will last until $data var is
|
||||
// unset (until the method is finished basically).
|
||||
if ($includetarget) {
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges,
|
||||
$this->analysabletarget);
|
||||
} else {
|
||||
$data = $timesplitting->calculate($sampleids, $this->get_samples_origin(), $this->indicators, $ranges);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$dataset->close_process();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
@@ -610,11 +620,16 @@ abstract class base {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Add extra metadata.
|
||||
$this->add_model_metadata($data);
|
||||
try {
|
||||
// Add extra metadata.
|
||||
$this->add_model_metadata($data);
|
||||
|
||||
// Write all calculated data to a file.
|
||||
$file = $dataset->store($data);
|
||||
// Write all calculated data to a file.
|
||||
$file = $dataset->store($data);
|
||||
} catch (\Throwable $e) {
|
||||
$dataset->close_process();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Flag the model + analysable + timesplitting as analysed.
|
||||
$dataset->close_process();
|
||||
|
||||
@@ -196,7 +196,7 @@ abstract class base extends \core_analytics\calculable {
|
||||
$message->component = 'moodle';
|
||||
$message->name = 'insights';
|
||||
|
||||
$message->userfrom = get_admin();
|
||||
$message->userfrom = \core_user::get_noreply_user();
|
||||
$message->userto = $user;
|
||||
|
||||
$insighturl = new \moodle_url('/report/insights/insights.php?modelid=' . $modelid . '&contextid=' . $context->id);
|
||||
|
||||
@@ -1524,7 +1524,10 @@ class model {
|
||||
// 1 db read per context.
|
||||
$this->purge_insights_cache();
|
||||
|
||||
$this->model->trained = 0;
|
||||
if (!$this->is_static()) {
|
||||
$this->model->trained = 0;
|
||||
}
|
||||
|
||||
$this->model->timemodified = time();
|
||||
$this->model->usermodified = $USER->id;
|
||||
$DB->update_record('analytics_models', $this->model);
|
||||
|
||||
@@ -168,10 +168,34 @@ class analytics_model_testcase extends advanced_testcase {
|
||||
$this->assertEmpty($DB->count_records('analytics_predict_samples'));
|
||||
$this->assertEmpty($DB->count_records('analytics_used_files'));
|
||||
|
||||
// Check that the model is marked as not trained after clearing (as it is not a static one).
|
||||
$this->assertEquals(0, $DB->get_field('analytics_models', 'trained', array('id' => $this->modelobj->id)));
|
||||
|
||||
set_config('enabled_stores', '', 'tool_log');
|
||||
get_log_manager(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test behaviour of {\core_analytics\model::clear()} for static models.
|
||||
*/
|
||||
public function test_clear_static() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$statictarget = new test_static_target_shortname();
|
||||
$indicators['test_indicator_max'] = \core_analytics\manager::get_indicator('test_indicator_max');
|
||||
$model = \core_analytics\model::create($statictarget, $indicators, '\core\analytics\time_splitting\quarters');
|
||||
$modelobj = $model->get_model_obj();
|
||||
|
||||
// Static models are always considered trained.
|
||||
$this->assertEquals(1, $DB->get_field('analytics_models', 'trained', array('id' => $modelobj->id)));
|
||||
|
||||
$model->clear();
|
||||
|
||||
// Check that the model is still marked as trained even after clearing.
|
||||
$this->assertEquals(1, $DB->get_field('analytics_models', 'trained', array('id' => $modelobj->id)));
|
||||
}
|
||||
|
||||
public function test_model_manager() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
|
||||
@@ -99,6 +99,13 @@ class auth_db_testcase extends advanced_testcase {
|
||||
case 'mssql':
|
||||
set_config('type', 'mssqlnative', 'auth_db');
|
||||
set_config('sybasequoting', '1', 'auth_db');
|
||||
|
||||
// The native sqlsrv driver uses a comma as separator between host and port.
|
||||
$dbhost = $CFG->dbhost;
|
||||
if (!empty($dboptions['dbport'])) {
|
||||
$dbhost .= ',' . $dboptions['dbport'];
|
||||
}
|
||||
set_config('host', $dbhost, 'auth_db');
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -63,3 +63,36 @@ Feature: User must accept policy when logging in and signing up
|
||||
And I log in as "user1"
|
||||
And I open my profile in edit mode
|
||||
And the field "First name" matches value "User1"
|
||||
|
||||
Scenario Outline: Email validation during email registration
|
||||
Given the following config values are set as admin:
|
||||
| allowaccountssameemail | <allowsameemail> |
|
||||
| registerauth | email |
|
||||
| passwordpolicy | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| s1 | John | Doe | s1@example.com |
|
||||
And I am on site homepage
|
||||
And I follow "Log in"
|
||||
When I press "Create new account"
|
||||
And I set the following fields to these values:
|
||||
| Username | s2 |
|
||||
| Password | test |
|
||||
| Email address | <email1> |
|
||||
| Email (again) | <email2> |
|
||||
| First name | Jane |
|
||||
| Surname | Doe |
|
||||
And I press "Create my new account"
|
||||
Then I should <expect> "This email address is already registered. Perhaps you created an account in the past?"
|
||||
And I should <expect2> "Invalid email address"
|
||||
|
||||
Examples:
|
||||
| allowsameemail | email1 | email2 | expect | expect2 |
|
||||
| 0 | s1@example.com | s1@example.com | see | not see |
|
||||
| 0 | S1@EXAMPLE.COM | S1@EXAMPLE.COM | see | not see |
|
||||
| 0 | s1@example.com | S1@EXAMPLE.COM | see | not see |
|
||||
| 0 | s2@example.com | s1@example.com | not see | see |
|
||||
| 1 | s1@example.com | s1@example.com | not see | not see |
|
||||
| 1 | S1@EXAMPLE.COM | S1@EXAMPLE.COM | not see | not see |
|
||||
| 1 | s1@example.com | S1@EXAMPLE.COM | not see | not see |
|
||||
| 1 | s1@example.com | s2@example.com | not see | see |
|
||||
|
||||
@@ -36,11 +36,7 @@ $string['auth_ldap_contexts_key'] = 'Contexts';
|
||||
$string['auth_ldap_create_context'] = 'If you enable user creation with email confirmation, specify the context where users are created. This context should be different from other users to prevent security issues. You don\'t need to add this context to ldap_context-variable, Moodle will search for users from this context automatically.<br /><b>Note!</b> You have to modify the method user_create() in file auth/ldap/auth.php to make user creation work';
|
||||
$string['auth_ldap_create_context_key'] = 'Context for new users';
|
||||
$string['auth_ldap_create_error'] = 'Error creating user in LDAP.';
|
||||
$string['auth_ldapdescription'] = 'This method provides authentication against an external LDAP server.
|
||||
If the given username and password are valid, Moodle creates a new user
|
||||
entry in its database. This module can read user attributes from LDAP and prefill
|
||||
wanted fields in Moodle. For following logins only the username and
|
||||
password are checked.';
|
||||
$string['auth_ldapdescription'] = 'This method provides authentication against an external LDAP server. If the given username and password are valid, Moodle creates a new user entry in its database. This plugin can read user attributes from LDAP and prefill wanted fields in Moodle. For following logins only the username and password are checked.';
|
||||
$string['auth_ldap_expiration_desc'] = 'Select \'{$a->no}\' to disable expired password checking or \'{$a->ldapserver}\' to read the password expiry time directly from the LDAP server.';
|
||||
$string['auth_ldap_expiration_key'] = 'Expiry';
|
||||
$string['auth_ldap_expiration_warning_desc'] = 'Number of days before password expiry warning is issued.';
|
||||
|
||||
@@ -311,11 +311,11 @@ class backup_controller extends base_controller {
|
||||
// Basic/initial prevention against time/memory limits
|
||||
core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
|
||||
raise_memory_limit(MEMORY_EXTRA);
|
||||
// If this is not a course backup, inform the plan we are not
|
||||
// If this is not a course backup, or single activity backup (e.g. duplicate) inform the plan we are not
|
||||
// including all the activities for sure. This will affect any
|
||||
// task/step executed conditionally to stop including information
|
||||
// for section and activity backup. MDL-28180.
|
||||
if ($this->get_type() !== backup::TYPE_1COURSE) {
|
||||
if ($this->get_type() !== backup::TYPE_1COURSE && $this->get_type() !== backup::TYPE_1ACTIVITY) {
|
||||
$this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG);
|
||||
$this->plan->set_excluding_activities();
|
||||
}
|
||||
|
||||
@@ -326,11 +326,11 @@ class restore_controller extends base_controller {
|
||||
// Basic/initial prevention against time/memory limits
|
||||
core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
|
||||
raise_memory_limit(MEMORY_EXTRA);
|
||||
// If this is not a course restore, inform the plan we are not
|
||||
// If this is not a course restore or single activity restore (e.g. duplicate), inform the plan we are not
|
||||
// including all the activities for sure. This will affect any
|
||||
// task/step executed conditionally to stop processing information
|
||||
// for section and activity restore. MDL-28180.
|
||||
if ($this->get_type() !== backup::TYPE_1COURSE) {
|
||||
if ($this->get_type() !== backup::TYPE_1COURSE && $this->get_type() !== backup::TYPE_1ACTIVITY) {
|
||||
$this->log('notifying plan about excluded activities by type', backup::LOG_DEBUG);
|
||||
$this->plan->set_excluding_activities();
|
||||
}
|
||||
|
||||
@@ -794,12 +794,25 @@ class backup_badges_structure_step extends backup_structure_step {
|
||||
$badge = new backup_nested_element('badge', array('id'), array('name', 'description',
|
||||
'timecreated', 'timemodified', 'usercreated', 'usermodified', 'issuername',
|
||||
'issuerurl', 'issuercontact', 'expiredate', 'expireperiod', 'type', 'courseid',
|
||||
'message', 'messagesubject', 'attachment', 'notification', 'status', 'nextcron'));
|
||||
'message', 'messagesubject', 'attachment', 'notification', 'status', 'nextcron',
|
||||
'version', 'language', 'imageauthorname', 'imageauthoremail', 'imageauthorurl',
|
||||
'imagecaption'));
|
||||
|
||||
$criteria = new backup_nested_element('criteria');
|
||||
$criterion = new backup_nested_element('criterion', array('id'), array('badgeid',
|
||||
'criteriatype', 'method', 'description', 'descriptionformat'));
|
||||
|
||||
$endorsement = new backup_nested_element('endorsement', array('id'), array('badgeid',
|
||||
'issuername', 'issuerurl', 'issueremail', 'claimid', 'claimcomment', 'dateissued'));
|
||||
|
||||
$alignments = new backup_nested_element('alignments');
|
||||
$alignment = new backup_nested_element('alignment', array('id'), array('badgeid',
|
||||
'targetname', 'targeturl', 'targetdescription', 'targetframework', 'targetcode'));
|
||||
|
||||
$relatedbadges = new backup_nested_element('relatedbadges');
|
||||
$relatedbadge = new backup_nested_element('relatedbadge', array('id'), array('badgeid',
|
||||
'relatedbadgeid'));
|
||||
|
||||
$parameters = new backup_nested_element('parameters');
|
||||
$parameter = new backup_nested_element('parameter', array('id'), array('critid',
|
||||
'name', 'value', 'criteriatype'));
|
||||
@@ -815,6 +828,11 @@ class backup_badges_structure_step extends backup_structure_step {
|
||||
$criteria->add_child($criterion);
|
||||
$criterion->add_child($parameters);
|
||||
$parameters->add_child($parameter);
|
||||
$badge->add_child($endorsement);
|
||||
$badge->add_child($alignments);
|
||||
$alignments->add_child($alignment);
|
||||
$badge->add_child($relatedbadges);
|
||||
$relatedbadges->add_child($relatedbadge);
|
||||
$badge->add_child($manual_awards);
|
||||
$manual_awards->add_child($manual_award);
|
||||
|
||||
@@ -822,6 +840,10 @@ class backup_badges_structure_step extends backup_structure_step {
|
||||
|
||||
$badge->set_source_table('badge', array('courseid' => backup::VAR_COURSEID));
|
||||
$criterion->set_source_table('badge_criteria', array('badgeid' => backup::VAR_PARENTID));
|
||||
$endorsement->set_source_table('badge_endorsement', array('badgeid' => backup::VAR_PARENTID));
|
||||
|
||||
$alignment->set_source_table('badge_competencies', array('badgeid' => backup::VAR_PARENTID));
|
||||
$relatedbadge->set_source_table('badge_related', array('badgeid' => backup::VAR_PARENTID));
|
||||
|
||||
$parametersql = 'SELECT cp.*, c.criteriatype
|
||||
FROM {badge_criteria_param} cp JOIN {badge_criteria} c
|
||||
@@ -838,6 +860,10 @@ class backup_badges_structure_step extends backup_structure_step {
|
||||
$badge->annotate_ids('user', 'usermodified');
|
||||
$criterion->annotate_ids('badge', 'badgeid');
|
||||
$parameter->annotate_ids('criterion', 'critid');
|
||||
$endorsement->annotate_ids('badge', 'badgeid');
|
||||
$alignment->annotate_ids('badge', 'badgeid');
|
||||
$relatedbadge->annotate_ids('badge', 'badgeid');
|
||||
$relatedbadge->annotate_ids('badge', 'relatedbadgeid');
|
||||
$badge->annotate_files('badges', 'badgeimage', 'id');
|
||||
$manual_award->annotate_ids('badge', 'badgeid');
|
||||
$manual_award->annotate_ids('user', 'recipientid');
|
||||
@@ -2270,7 +2296,11 @@ class backup_questions_structure_step extends backup_structure_step {
|
||||
FROM {tag} t
|
||||
JOIN {tag_instance} ti ON ti.tagid = t.id
|
||||
WHERE ti.itemid = ?
|
||||
AND ti.itemtype = 'question'", array(backup::VAR_PARENTID));
|
||||
AND ti.itemtype = 'question'
|
||||
AND ti.component = 'core_question'",
|
||||
[
|
||||
backup::VAR_PARENTID
|
||||
]);
|
||||
|
||||
// don't need to annotate ids nor files
|
||||
// (already done by {@link backup_annotate_all_question_files}
|
||||
|
||||
@@ -2506,6 +2506,9 @@ class restore_badges_structure_step extends restore_structure_step {
|
||||
$paths[] = new restore_path_element('badge', '/badges/badge');
|
||||
$paths[] = new restore_path_element('criterion', '/badges/badge/criteria/criterion');
|
||||
$paths[] = new restore_path_element('parameter', '/badges/badge/criteria/criterion/parameters/parameter');
|
||||
$paths[] = new restore_path_element('endorsement', '/badges/badge/endorsement');
|
||||
$paths[] = new restore_path_element('alignment', '/badges/badge/alignments/alignment');
|
||||
$paths[] = new restore_path_element('relatedbadge', '/badges/badge/relatedbadges/relatedbadge');
|
||||
$paths[] = new restore_path_element('manual_award', '/badges/badge/manual_awards/manual_award');
|
||||
|
||||
return $paths;
|
||||
@@ -2550,13 +2553,87 @@ class restore_badges_structure_step extends restore_structure_step {
|
||||
'attachment' => $data->attachment,
|
||||
'notification' => $data->notification,
|
||||
'status' => BADGE_STATUS_INACTIVE,
|
||||
'nextcron' => $data->nextcron
|
||||
'nextcron' => $data->nextcron,
|
||||
'version' => $data->version,
|
||||
'language' => $data->language,
|
||||
'imageauthorname' => $data->imageauthorname,
|
||||
'imageauthoremail' => $data->imageauthoremail,
|
||||
'imageauthorurl' => $data->imageauthorurl,
|
||||
'imagecaption' => $data->imagecaption
|
||||
);
|
||||
|
||||
$newid = $DB->insert_record('badge', $params);
|
||||
$this->set_mapping('badge', $data->id, $newid, $restorefiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an endorsement for a badge.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return void
|
||||
*/
|
||||
public function process_endorsement($data) {
|
||||
global $DB;
|
||||
|
||||
$data = (object)$data;
|
||||
|
||||
$params = array(
|
||||
'badgeid' => $this->get_new_parentid('badge'),
|
||||
'issuername' => $data->issuername,
|
||||
'issuerurl' => $data->issuerurl,
|
||||
'issueremail' => $data->issueremail,
|
||||
'claimid' => $data->claimid,
|
||||
'claimcomment' => $data->claimcomment,
|
||||
'dateissued' => $this->apply_date_offset($data->dateissued)
|
||||
);
|
||||
$newid = $DB->insert_record('badge_endorsement', $params);
|
||||
$this->set_mapping('endorsement', $data->id, $newid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link to related badges for a badge. This relies on post processing in after_execute().
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return void
|
||||
*/
|
||||
public function process_relatedbadge($data) {
|
||||
global $DB;
|
||||
|
||||
$data = (object)$data;
|
||||
$relatedbadgeid = $data->relatedbadgeid;
|
||||
|
||||
if ($relatedbadgeid) {
|
||||
// Only backup and restore related badges if they are contained in the backup file.
|
||||
$params = array(
|
||||
'badgeid' => $this->get_new_parentid('badge'),
|
||||
'relatedbadgeid' => $relatedbadgeid
|
||||
);
|
||||
$newid = $DB->insert_record('badge_related', $params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link to an alignment for a badge.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return void
|
||||
*/
|
||||
public function process_alignment($data) {
|
||||
global $DB;
|
||||
|
||||
$data = (object)$data;
|
||||
$params = array(
|
||||
'badgeid' => $this->get_new_parentid('badge'),
|
||||
'targetname' => $data->targetname,
|
||||
'targeturl' => $data->targeturl,
|
||||
'targetdescription' => $data->targetdescription,
|
||||
'targetframework' => $data->targetframework,
|
||||
'targetcode' => $data->targetcode
|
||||
);
|
||||
$newid = $DB->insert_record('badge_competencies', $params);
|
||||
$this->set_mapping('alignment', $data->id, $newid);
|
||||
}
|
||||
|
||||
public function process_criterion($data) {
|
||||
global $DB;
|
||||
|
||||
@@ -2569,6 +2646,7 @@ class restore_badges_structure_step extends restore_structure_step {
|
||||
'description' => isset($data->description) ? $data->description : '',
|
||||
'descriptionformat' => isset($data->descriptionformat) ? $data->descriptionformat : 0,
|
||||
);
|
||||
|
||||
$newid = $DB->insert_record('badge_criteria', $params);
|
||||
$this->set_mapping('criterion', $data->id, $newid);
|
||||
}
|
||||
@@ -2634,8 +2712,38 @@ class restore_badges_structure_step extends restore_structure_step {
|
||||
}
|
||||
|
||||
protected function after_execute() {
|
||||
global $DB;
|
||||
// Add related files.
|
||||
$this->add_related_files('badges', 'badgeimage', 'badge');
|
||||
|
||||
$badgeid = $this->get_new_parentid('badge');
|
||||
// Remap any related badges.
|
||||
// We do this in the DB directly because this is backup/restore it is not valid to call into
|
||||
// the component API.
|
||||
$params = array('badgeid' => $badgeid);
|
||||
$query = "SELECT DISTINCT br.id, br.badgeid, br.relatedbadgeid
|
||||
FROM {badge_related} br
|
||||
WHERE (br.badgeid = :badgeid)";
|
||||
$relatedbadges = $DB->get_records_sql($query, $params);
|
||||
$newrelatedids = [];
|
||||
foreach ($relatedbadges as $relatedbadge) {
|
||||
$relatedid = $this->get_mappingid('badge', $relatedbadge->relatedbadgeid);
|
||||
$params['relatedbadgeid'] = $relatedbadge->relatedbadgeid;
|
||||
$DB->delete_records_select('badge_related', '(badgeid = :badgeid AND relatedbadgeid = :relatedbadgeid)', $params);
|
||||
if ($relatedid) {
|
||||
$newrelatedids[] = $relatedid;
|
||||
}
|
||||
}
|
||||
if (!empty($newrelatedids)) {
|
||||
$relatedbadges = [];
|
||||
foreach ($newrelatedids as $relatedid) {
|
||||
$relatedbadge = new stdClass();
|
||||
$relatedbadge->badgeid = $badgeid;
|
||||
$relatedbadge->relatedbadgeid = $relatedid;
|
||||
$relatedbadges[] = $relatedbadge;
|
||||
}
|
||||
$DB->insert_records('badge_related', $relatedbadges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -580,6 +580,17 @@ abstract class backup_controller_dbops extends backup_dbops {
|
||||
'backup_import_competencies' => 'competencies'
|
||||
);
|
||||
self::apply_admin_config_defaults($controller, $settings, true);
|
||||
if ((!$controller->get_interactive()) &&
|
||||
$controller->get_type() == backup::TYPE_1ACTIVITY) {
|
||||
// This is duplicate - there is no concept of defaults - these settings must be on.
|
||||
$settings = array(
|
||||
'activities',
|
||||
'blocks',
|
||||
'filters',
|
||||
'questionbank'
|
||||
);
|
||||
self::force_enable_settings($controller, $settings);
|
||||
}
|
||||
break;
|
||||
case backup::MODE_AUTOMATED:
|
||||
// Load the automated defaults.
|
||||
@@ -607,6 +618,30 @@ abstract class backup_controller_dbops extends backup_dbops {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn these settings on. No defaults from admin settings.
|
||||
*
|
||||
* @param backup_controller $controller
|
||||
* @param array $settings a map from admin config names to setting names (Config name => Setting name)
|
||||
*/
|
||||
private static function force_enable_settings(backup_controller $controller, array $settings) {
|
||||
$plan = $controller->get_plan();
|
||||
foreach ($settings as $config => $settingname) {
|
||||
$value = true;
|
||||
if ($plan->setting_exists($settingname)) {
|
||||
$setting = $plan->get_setting($settingname);
|
||||
// We do not allow this setting to be locked for a duplicate function.
|
||||
if ($setting->get_status() !== base_setting::NOT_LOCKED) {
|
||||
$setting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
$setting->set_value($value);
|
||||
$setting->set_status(base_setting::LOCKED_BY_CONFIG);
|
||||
} else {
|
||||
$controller->log('Unknown setting: ' . $setting, BACKUP::LOG_DEBUG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller settings default values from the admin config.
|
||||
*
|
||||
|
||||
@@ -183,6 +183,18 @@ abstract class restore_controller_dbops extends restore_dbops {
|
||||
);
|
||||
self::apply_admin_config_defaults($controller, $settings, true);
|
||||
}
|
||||
if ($controller->get_mode() == backup::MODE_IMPORT &&
|
||||
(!$controller->get_interactive()) &&
|
||||
$controller->get_type() == backup::TYPE_1ACTIVITY) {
|
||||
// This is duplicate - there is no concept of defaults - these settings must be on.
|
||||
$settings = array(
|
||||
'activities',
|
||||
'blocks',
|
||||
'filters',
|
||||
'questionbank'
|
||||
);
|
||||
self::force_enable_settings($controller, $settings);
|
||||
};
|
||||
|
||||
// Add some dependencies.
|
||||
$plan = $controller->get_plan();
|
||||
@@ -233,6 +245,30 @@ abstract class restore_controller_dbops extends restore_dbops {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn these settings on. No defaults from admin settings.
|
||||
*
|
||||
* @param restore_controller $controller
|
||||
* @param array $settings a map from admin config names to setting names (Config name => Setting name)
|
||||
*/
|
||||
private static function force_enable_settings(restore_controller $controller, array $settings) {
|
||||
$plan = $controller->get_plan();
|
||||
foreach ($settings as $config => $settingname) {
|
||||
$value = true;
|
||||
if ($plan->setting_exists($settingname)) {
|
||||
$setting = $plan->get_setting($settingname);
|
||||
// We do not allow this setting to be locked for a duplicate function.
|
||||
if ($setting->get_status() !== base_setting::NOT_LOCKED) {
|
||||
$setting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
$setting->set_value($value);
|
||||
$setting->set_status(base_setting::LOCKED_BY_CONFIG);
|
||||
} else {
|
||||
$controller->log('Unknown setting: ' . $settingname, BACKUP::LOG_DEBUG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller settings default values from the admin config.
|
||||
*
|
||||
|
||||
@@ -238,12 +238,12 @@ class restore_log_rule implements processable {
|
||||
protected function build_regexp($expression, $tokens) {
|
||||
// Replace to temp (and preg_quote() safe) placeholders
|
||||
foreach ($tokens as $token) {
|
||||
$expression = preg_replace('~' . preg_quote($token, '~') . '~', '#@@#@@#', $expression, 1);
|
||||
$expression = preg_replace('~' . preg_quote($token, '~') . '~', '%@@%@@%', $expression, 1);
|
||||
}
|
||||
// quote the expression
|
||||
$expression = preg_quote($expression, '~');
|
||||
// Replace all the placeholders
|
||||
$expression = preg_replace('~#@@#@@#~', '(.*)', $expression);
|
||||
$expression = preg_replace('~%@@%@@%~', '(.*)', $expression);
|
||||
return '~' . $expression . '~';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,4 +49,20 @@ class backup_restore_log_rule_testcase extends basic_testcase {
|
||||
// But the original log has been kept unmodified by the process() call.
|
||||
$this->assertEquals($originallog, $log);
|
||||
}
|
||||
|
||||
public function test_build_regexp() {
|
||||
$original = 'Any (string) with [placeholders] like {this} and {this}. [end].';
|
||||
$expectation = '~Any \(string\) with (.*) like (.*) and (.*)\. (.*)\.~';
|
||||
|
||||
$lr = new restore_log_rule('this', 'doesnt', 'matter', 'here');
|
||||
$class = new ReflectionClass('restore_log_rule');
|
||||
|
||||
$method = $class->getMethod('extract_tokens');
|
||||
$method->setAccessible(true);
|
||||
$tokens = $method->invoke($lr, $original);
|
||||
|
||||
$method = $class->getMethod('build_regexp');
|
||||
$method->setAccessible(true);
|
||||
$this->assertSame($expectation, $method->invoke($lr, $original, $tokens));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
@@ -82,7 +81,7 @@ abstract class setting_dependency {
|
||||
* Destroy all circular references. It helps PHP 5.2 a lot!
|
||||
*/
|
||||
public function destroy() {
|
||||
// No need to destroy anything recursively here, direct reset
|
||||
// No need to destroy anything recursively here, direct reset.
|
||||
$this->setting = null;
|
||||
$this->dependentsetting = null;
|
||||
}
|
||||
@@ -94,16 +93,19 @@ abstract class setting_dependency {
|
||||
* @return bool
|
||||
*/
|
||||
final public function process_change($changetype, $oldvalue) {
|
||||
// Check the type of change requested
|
||||
// Check the type of change requested.
|
||||
switch ($changetype) {
|
||||
// Process a status change
|
||||
case base_setting::CHANGED_STATUS: return $this->process_status_change($oldvalue);
|
||||
// Process a visibility change
|
||||
case base_setting::CHANGED_VISIBILITY: return $this->process_visibility_change($oldvalue);
|
||||
// Process a value change
|
||||
case base_setting::CHANGED_VALUE: return $this->process_value_change($oldvalue);
|
||||
// Process a status change.
|
||||
case base_setting::CHANGED_STATUS:
|
||||
return $this->process_status_change($oldvalue);
|
||||
// Process a visibility change.
|
||||
case base_setting::CHANGED_VISIBILITY:
|
||||
return $this->process_visibility_change($oldvalue);
|
||||
// Process a value change.
|
||||
case base_setting::CHANGED_VALUE:
|
||||
return $this->process_value_change($oldvalue);
|
||||
}
|
||||
// Throw an exception if we get this far
|
||||
// Throw an exception if we get this far.
|
||||
throw new backup_ui_exception('unknownchangetype');
|
||||
}
|
||||
/**
|
||||
@@ -112,11 +114,11 @@ abstract class setting_dependency {
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_visibility_change($oldvisibility) {
|
||||
// Store the current dependent settings visibility for comparison
|
||||
// Store the current dependent settings visibility for comparison.
|
||||
$prevalue = $this->dependentsetting->get_visibility();
|
||||
// Set it regardless of whether we need to
|
||||
// Set it regardless of whether we need to.
|
||||
$this->dependentsetting->set_visibility($this->setting->get_visibility());
|
||||
// Return true if it changed
|
||||
// Return true if it changed.
|
||||
return ($prevalue != $this->dependentsetting->get_visibility());
|
||||
}
|
||||
/**
|
||||
@@ -182,15 +184,16 @@ class setting_dependency_disabledif_equals extends setting_dependency {
|
||||
*/
|
||||
public function __construct(base_setting $setting, base_setting $dependentsetting, $value, $defaultvalue = false) {
|
||||
parent::__construct($setting, $dependentsetting, $defaultvalue);
|
||||
$this->value = ($value)?(string)$value:0;
|
||||
$this->value = ($value) ? (string)$value : 0;
|
||||
}
|
||||
/**
|
||||
* Returns true if the dependent setting is locked by this setting_dependency.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked() {
|
||||
// If the setting is locked or the dependent setting should be locked then return true
|
||||
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || $this->setting->get_value() == $this->value) {
|
||||
// If the setting is locked or the dependent setting should be locked then return true.
|
||||
if ($this->setting->get_status() !== base_setting::NOT_LOCKED ||
|
||||
$this->evaluate_disabled_condition($this->setting->get_value())) {
|
||||
return true;
|
||||
}
|
||||
// Else the dependent setting is not locked by this setting_dependency.
|
||||
@@ -208,17 +211,25 @@ class setting_dependency_disabledif_equals extends setting_dependency {
|
||||
return false;
|
||||
}
|
||||
$prevalue = $this->dependentsetting->get_value();
|
||||
// If the setting is the desired value enact the dependency
|
||||
if ($this->setting->get_value() == $this->value) {
|
||||
// If the setting is the desired value enact the dependency.
|
||||
$settingvalue = $this->setting->get_value();
|
||||
if ($this->evaluate_disabled_condition($settingvalue)) {
|
||||
// The dependent setting needs to be locked by hierachy and set to the
|
||||
// default value.
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
$this->dependentsetting->set_value($this->defaultvalue);
|
||||
|
||||
// For checkboxes the default value is false, but when the setting is
|
||||
// locked, the value should inherit from the parent setting.
|
||||
if ($this->defaultvalue === false) {
|
||||
$this->dependentsetting->set_value($settingvalue);
|
||||
} else {
|
||||
$this->dependentsetting->set_value($this->defaultvalue);
|
||||
}
|
||||
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
|
||||
// We can unlock the dependent setting
|
||||
// We can unlock the dependent setting.
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
// Return true if the value has changed for the dependent setting
|
||||
// Return true if the value has changed for the dependent setting.
|
||||
return ($prevalue != $this->dependentsetting->get_value());
|
||||
}
|
||||
/**
|
||||
@@ -227,17 +238,18 @@ class setting_dependency_disabledif_equals extends setting_dependency {
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_status_change($oldstatus) {
|
||||
// Store the dependent status
|
||||
// Store the dependent status.
|
||||
$prevalue = $this->dependentsetting->get_status();
|
||||
// Store the current status
|
||||
// Store the current status.
|
||||
$currentstatus = $this->setting->get_status();
|
||||
if ($currentstatus == base_setting::NOT_LOCKED) {
|
||||
if ($prevalue == base_setting::LOCKED_BY_HIERARCHY && $this->setting->get_value() != $this->value) {
|
||||
// Dependency has changes, is not fine, unlock the dependent setting
|
||||
if ($prevalue == base_setting::LOCKED_BY_HIERARCHY &&
|
||||
!$this->evaluate_disabled_condition($this->setting->get_value())) {
|
||||
// Dependency has changes, is not fine, unlock the dependent setting.
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
} else {
|
||||
// Make sure the dependent setting is also locked, in this case by hierarchy
|
||||
// Make sure the dependent setting is also locked, in this case by hierarchy.
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
}
|
||||
// Return true if the dependent setting has changed.
|
||||
@@ -248,17 +260,17 @@ class setting_dependency_disabledif_equals extends setting_dependency {
|
||||
* @return bool True if there were changes
|
||||
*/
|
||||
public function enforce() {
|
||||
// This will be set to true if ANYTHING changes
|
||||
// This will be set to true if ANYTHING changes.
|
||||
$changes = false;
|
||||
// First process any value changes
|
||||
// First process any value changes.
|
||||
if ($this->process_value_change($this->setting->get_value())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Second process any status changes
|
||||
// Second process any status changes.
|
||||
if ($this->process_status_change($this->setting->get_status())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Finally process visibility changes
|
||||
// Finally process visibility changes.
|
||||
if ($this->process_visibility_change($this->setting->get_visibility())) {
|
||||
$changes = true;
|
||||
}
|
||||
@@ -271,152 +283,72 @@ class setting_dependency_disabledif_equals extends setting_dependency {
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'eq',
|
||||
'value'=>$this->value
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'eq',
|
||||
'value' => $this->value
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
|
||||
* This function should be abstract, but there will probably be existing sub-classes so we must provide a default
|
||||
* implementation.
|
||||
* @param mixed $value The value of the parent setting.
|
||||
* @return bool
|
||||
*/
|
||||
protected function evaluate_disabled_condition($value) {
|
||||
return $value == $this->value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dependency that disables the secondary setting if the primary setting is
|
||||
* not equal to the provided value
|
||||
*
|
||||
* @copyright 2011 Darko Miletic <dmiletic@moodlerooms.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class setting_dependency_disabledif_not_equals extends setting_dependency_disabledif_equals {
|
||||
|
||||
/**
|
||||
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
|
||||
* @param mixed $value The value of the parent setting.
|
||||
* @return bool
|
||||
*/
|
||||
protected function evaluate_disabled_condition($value) {
|
||||
return $value != $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of properties suitable to be used to define a moodleforms
|
||||
* disabled command
|
||||
* @return array
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'notequal',
|
||||
'value' => $this->value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dependency that disables the secondary setting if the primary setting is
|
||||
* not equal to the provided value
|
||||
*
|
||||
* @copyright 2011 Darko Miletic <dmiletic@moodlerooms.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class setting_dependency_disabledif_not_equals extends setting_dependency_disabledif_equals {
|
||||
/**
|
||||
* Enforces the dependency if required.
|
||||
* @return bool True if there were changes
|
||||
*/
|
||||
public function enforce() {
|
||||
// This will be set to true if ANYTHING changes
|
||||
$changes = false;
|
||||
// First process any value changes
|
||||
if (!$this->process_value_change($this->setting->get_value())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Second process any status changes
|
||||
if ($this->process_status_change($this->setting->get_status())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Finally process visibility changes
|
||||
if ($this->process_visibility_change($this->setting->get_visibility())) {
|
||||
$changes = true;
|
||||
}
|
||||
return $changes;
|
||||
}
|
||||
/**
|
||||
* Returns an array of properties suitable to be used to define a moodleforms
|
||||
* disabled command
|
||||
* @return array
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'notequal',
|
||||
'value'=>$this->value
|
||||
);
|
||||
}
|
||||
}
|
||||
* Disable if a value is in a list.
|
||||
*/
|
||||
class setting_dependency_disabledif_in_array extends setting_dependency_disabledif_equals {
|
||||
|
||||
//with array
|
||||
class setting_dependency_disabledif_equals2 extends setting_dependency {
|
||||
/**
|
||||
* The value to compare to
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
/**
|
||||
* Creates the dependency
|
||||
*
|
||||
* @param base_setting $setting
|
||||
* @param base_setting $dependentsetting
|
||||
* @param mixed $value
|
||||
* @param mixed $defaultvalue
|
||||
*/
|
||||
public function __construct(base_setting $setting, base_setting $dependentsetting, array $value, $defaultvalue = false) {
|
||||
parent::__construct($setting, $dependentsetting, $defaultvalue);
|
||||
$this->value = $value;
|
||||
}
|
||||
/**
|
||||
* Returns true if the dependent setting is locked by this setting_dependency.
|
||||
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
|
||||
* @param mixed $value The value of the parent setting.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked() {
|
||||
// If the setting is locked or the dependent setting should be locked then return true
|
||||
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || in_array($this->setting->get_value(), $this->value)) {
|
||||
return true;
|
||||
}
|
||||
// Else the dependent setting is not locked by this setting_dependency.
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Processes a value change in the primary setting
|
||||
* @param mixed $oldvalue
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_value_change($oldvalue) {
|
||||
$prevalue = $this->dependentsetting->get_value();
|
||||
// If the setting is the desired value enact the dependency
|
||||
if (in_array($this->setting->get_value(), $this->value)) {
|
||||
// The dependent setting needs to be locked by hierachy and set to the
|
||||
// default value.
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
$this->dependentsetting->set_value($this->defaultvalue);
|
||||
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
|
||||
// We can unlock the dependent setting
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
// Return true if the value has changed for the dependent setting
|
||||
return ($prevalue != $this->dependentsetting->get_value());
|
||||
}
|
||||
/**
|
||||
* Processes a status change in the primary setting
|
||||
* @param mixed $oldstatus
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_status_change($oldstatus) {
|
||||
// Store the dependent status
|
||||
$prevalue = $this->dependentsetting->get_status();
|
||||
// Store the current status
|
||||
$currentstatus = $this->setting->get_status();
|
||||
if ($currentstatus == base_setting::NOT_LOCKED) {
|
||||
if ($prevalue == base_setting::LOCKED_BY_HIERARCHY && !in_array($this->setting->get_value(), $this->value)) {
|
||||
// Dependency has changes, is not fine, unlock the dependent setting
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
} else {
|
||||
// Make sure the dependent setting is also locked, in this case by hierarchy
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
}
|
||||
// Return true if the dependent setting has changed.
|
||||
return ($prevalue != $this->dependentsetting->get_status());
|
||||
}
|
||||
/**
|
||||
* Enforces the dependency if required.
|
||||
* @return bool True if there were changes
|
||||
*/
|
||||
public function enforce() {
|
||||
// This will be set to true if ANYTHING changes
|
||||
$changes = false;
|
||||
// First process any value changes
|
||||
if ($this->process_value_change($this->setting->get_value())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Second process any status changes
|
||||
if ($this->process_status_change($this->setting->get_status())) {
|
||||
$changes = true;
|
||||
}
|
||||
// Finally process visibility changes
|
||||
if ($this->process_visibility_change($this->setting->get_visibility())) {
|
||||
$changes = true;
|
||||
}
|
||||
return $changes;
|
||||
protected function evaluate_disabled_condition($value) {
|
||||
return in_array($value, $this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of properties suitable to be used to define a moodleforms
|
||||
* disabled command
|
||||
@@ -424,14 +356,19 @@ class setting_dependency_disabledif_equals2 extends setting_dependency {
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'eq',
|
||||
'value'=>$this->value
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'eq',
|
||||
'value' => $this->value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is here for backwards compatibility (terrible name).
|
||||
*/
|
||||
class setting_dependency_disabledif_equals2 extends setting_dependency_disabledif_in_array {
|
||||
}
|
||||
|
||||
/**
|
||||
* A dependency that disables the secondary element if the primary element is
|
||||
@@ -452,9 +389,9 @@ class setting_dependency_disabledif_checked extends setting_dependency_disabledi
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'checked'
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'checked'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -478,9 +415,9 @@ class setting_dependency_disabledif_not_checked extends setting_dependency_disab
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'notchecked'
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'notchecked'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -497,6 +434,16 @@ class setting_dependency_disabledif_not_empty extends setting_dependency_disable
|
||||
parent::__construct($setting, $dependentsetting, false, $defaultvalue);
|
||||
$this->value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
|
||||
* @param mixed $value The value of the parent setting.
|
||||
* @return bool
|
||||
*/
|
||||
protected function evaluate_disabled_condition($value) {
|
||||
return !empty($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of properties suitable to be used to define a moodleforms
|
||||
* disabled command
|
||||
@@ -504,50 +451,12 @@ class setting_dependency_disabledif_not_empty extends setting_dependency_disable
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'notequal',
|
||||
'value'=>''
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'notequal',
|
||||
'value' => ''
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Processes a value change in the primary setting
|
||||
* @param mixed $oldvalue
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_value_change($oldvalue) {
|
||||
$prevalue = $this->dependentsetting->get_value();
|
||||
// If the setting is the desired value enact the dependency
|
||||
$value = $this->setting->get_value();
|
||||
if (!empty($value)) {
|
||||
// The dependent setting needs to be locked by hierachy and set to the
|
||||
// default value.
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
if ($this->defaultvalue === false) {
|
||||
$this->dependentsetting->set_value($value);
|
||||
} else {
|
||||
$this->dependentsetting->set_value($this->defaultvalue);
|
||||
}
|
||||
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
|
||||
// We can unlock the dependent setting
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
// Return true if the value has changed for the dependent setting
|
||||
return ($prevalue != $this->dependentsetting->get_value());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the dependent setting is locked by this setting_dependency.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked() {
|
||||
// If the setting is locked or the dependent setting should be locked then return true
|
||||
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || !empty($value)) {
|
||||
return true;
|
||||
}
|
||||
// Else the dependent setting is not locked by this setting_dependency.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -562,6 +471,16 @@ class setting_dependency_disabledif_empty extends setting_dependency_disabledif_
|
||||
parent::__construct($setting, $dependentsetting, false, $defaultvalue);
|
||||
$this->value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the current value of the setting and return true if the dependent setting should be locked or false.
|
||||
* @param mixed $value The value of the parent setting.
|
||||
* @return bool
|
||||
*/
|
||||
protected function evaluate_disabled_condition($value) {
|
||||
return empty($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of properties suitable to be used to define a moodleforms
|
||||
* disabled command
|
||||
@@ -569,47 +488,10 @@ class setting_dependency_disabledif_empty extends setting_dependency_disabledif_
|
||||
*/
|
||||
public function get_moodleform_properties() {
|
||||
return array(
|
||||
'setting'=>$this->dependentsetting->get_ui_name(),
|
||||
'dependenton'=>$this->setting->get_ui_name(),
|
||||
'condition'=>'notequal',
|
||||
'value'=>''
|
||||
'setting' => $this->dependentsetting->get_ui_name(),
|
||||
'dependenton' => $this->setting->get_ui_name(),
|
||||
'condition' => 'notequal',
|
||||
'value' => ''
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Processes a value change in the primary setting
|
||||
* @param mixed $oldvalue
|
||||
* @return bool
|
||||
*/
|
||||
protected function process_value_change($oldvalue) {
|
||||
$prevalue = $this->dependentsetting->get_value();
|
||||
// If the setting is the desired value enact the dependency
|
||||
$value = $this->setting->get_value();
|
||||
if (empty($value)) {
|
||||
// The dependent setting needs to be locked by hierachy and set to the
|
||||
// default value.
|
||||
$this->dependentsetting->set_status(base_setting::LOCKED_BY_HIERARCHY);
|
||||
if ($this->defaultvalue === false) {
|
||||
$this->dependentsetting->set_value($value);
|
||||
} else {
|
||||
$this->dependentsetting->set_value($this->defaultvalue);
|
||||
}
|
||||
} else if ($this->dependentsetting->get_status() == base_setting::LOCKED_BY_HIERARCHY) {
|
||||
// We can unlock the dependent setting
|
||||
$this->dependentsetting->set_status(base_setting::NOT_LOCKED);
|
||||
}
|
||||
// Return true if the value has changed for the dependent setting
|
||||
return ($prevalue != $this->dependentsetting->get_value());
|
||||
}
|
||||
/**
|
||||
* Returns true if the dependent setting is locked by this setting_dependency.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_locked() {
|
||||
// If the setting is locked or the dependent setting should be locked then return true
|
||||
if ($this->setting->get_status() !== base_setting::NOT_LOCKED || empty($value)) {
|
||||
return true;
|
||||
}
|
||||
// Else the dependent setting is not locked by this setting_dependency.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class backp_settings_testcase extends basic_testcase {
|
||||
/**
|
||||
* test base_setting class
|
||||
*/
|
||||
function test_base_setting() {
|
||||
public function test_base_setting() {
|
||||
// Instantiate base_setting and check everything
|
||||
$bs = new mock_base_setting('test', base_setting::IS_BOOLEAN);
|
||||
$this->assertTrue($bs instanceof base_setting);
|
||||
@@ -290,10 +290,54 @@ class backp_settings_testcase extends basic_testcase {
|
||||
$this->assertEquals($ubs3->get_status(), $ubs1->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that locked and unlocked states on dependent backup settings at the same level
|
||||
* correctly do not flow from the parent to the child setting when the setting is locked by permissions.
|
||||
*/
|
||||
public function test_dependency_empty_locked_by_permission_child_is_not_unlocked() {
|
||||
// Check dependencies are working ok.
|
||||
$bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, 2);
|
||||
$bs1->set_level(1);
|
||||
$bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, 2);
|
||||
$bs2->set_level(1); // Same level *must* work.
|
||||
$bs1->add_dependency($bs2, setting_dependency::DISABLED_EMPTY);
|
||||
|
||||
$bs1->set_status(base_setting::LOCKED_BY_PERMISSION);
|
||||
$this->assertEquals(base_setting::LOCKED_BY_HIERARCHY, $bs2->get_status());
|
||||
$this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs1->get_status());
|
||||
$bs2->set_status(base_setting::LOCKED_BY_PERMISSION);
|
||||
$this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs1->get_status());
|
||||
|
||||
// Unlocking the parent should NOT unlock the child.
|
||||
$bs1->set_status(base_setting::NOT_LOCKED);
|
||||
|
||||
$this->assertEquals(base_setting::LOCKED_BY_PERMISSION, $bs2->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that locked and unlocked states on dependent backup settings at the same level
|
||||
* correctly do flow from the parent to the child setting when the setting is locked by config.
|
||||
*/
|
||||
public function test_dependency_not_empty_locked_by_config_parent_is_unlocked() {
|
||||
$bs1 = new mock_backup_setting('test1', base_setting::IS_INTEGER, 0);
|
||||
$bs1->set_level(1);
|
||||
$bs2 = new mock_backup_setting('test2', base_setting::IS_INTEGER, 0);
|
||||
$bs2->set_level(1); // Same level *must* work.
|
||||
$bs1->add_dependency($bs2, setting_dependency::DISABLED_NOT_EMPTY);
|
||||
|
||||
$bs1->set_status(base_setting::LOCKED_BY_CONFIG);
|
||||
$this->assertEquals(base_setting::LOCKED_BY_HIERARCHY, $bs2->get_status());
|
||||
$this->assertEquals(base_setting::LOCKED_BY_CONFIG, $bs1->get_status());
|
||||
|
||||
// Unlocking the parent should unlock the child.
|
||||
$bs1->set_status(base_setting::NOT_LOCKED);
|
||||
$this->assertEquals(base_setting::NOT_LOCKED, $bs2->get_status());
|
||||
}
|
||||
|
||||
/**
|
||||
* test backup_setting class
|
||||
*/
|
||||
function test_backup_setting() {
|
||||
public function test_backup_setting() {
|
||||
// Instantiate backup_setting class and set level
|
||||
$bs = new mock_backup_setting('test', base_setting::IS_INTEGER, null);
|
||||
$bs->set_level(1);
|
||||
@@ -340,7 +384,7 @@ class backp_settings_testcase extends basic_testcase {
|
||||
/**
|
||||
* test activity_backup_setting class
|
||||
*/
|
||||
function test_activity_backup_setting() {
|
||||
public function test_activity_backup_setting() {
|
||||
$bs = new mock_activity_backup_setting('test', base_setting::IS_INTEGER, null);
|
||||
$this->assertEquals($bs->get_level(), backup_setting::ACTIVITY_LEVEL);
|
||||
|
||||
@@ -355,7 +399,7 @@ class backp_settings_testcase extends basic_testcase {
|
||||
/**
|
||||
* test section_backup_setting class
|
||||
*/
|
||||
function test_section_backup_setting() {
|
||||
public function test_section_backup_setting() {
|
||||
$bs = new mock_section_backup_setting('test', base_setting::IS_INTEGER, null);
|
||||
$this->assertEquals($bs->get_level(), backup_setting::SECTION_LEVEL);
|
||||
|
||||
@@ -370,7 +414,7 @@ class backp_settings_testcase extends basic_testcase {
|
||||
/**
|
||||
* test course_backup_setting class
|
||||
*/
|
||||
function test_course_backup_setting() {
|
||||
public function test_course_backup_setting() {
|
||||
$bs = new mock_course_backup_setting('test', base_setting::IS_INTEGER, null);
|
||||
$this->assertEquals($bs->get_level(), backup_setting::COURSE_LEVEL);
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ Feature: Duplicate activities
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And I log in as "admin"
|
||||
And I set the following administration settings values:
|
||||
| backup_import_activities | 0 |
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I add a "Database" to section "1" and I fill the form with:
|
||||
|
||||
@@ -95,7 +95,7 @@ if ($badge->status != BADGE_STATUS_INACTIVE) {
|
||||
$json['endorsement'] = $endorsementurl->out(false);
|
||||
}
|
||||
|
||||
$competencies = $badge->get_alignment();
|
||||
$competencies = $badge->get_alignments();
|
||||
if (!empty($competencies)) {
|
||||
foreach ($competencies as $item) {
|
||||
$alignment = array('targetName' => $item->targetname, 'targetUrl' => $item->targeturl);
|
||||
@@ -123,4 +123,4 @@ if ($badge->status != BADGE_STATUS_INACTIVE) {
|
||||
$json['error'] = get_string('error:relatedbadgedoesntexist', 'badges');
|
||||
}
|
||||
echo $OUTPUT->header();
|
||||
echo json_encode($json);
|
||||
echo json_encode($json);
|
||||
|
||||
@@ -150,7 +150,7 @@ class core_badges_external extends external_api {
|
||||
// Create a badge instance to be able to get the endorsement and other info.
|
||||
$badgeinstance = new badge($badge->id);
|
||||
$endorsement = $badgeinstance->get_endorsement();
|
||||
$competencies = $badgeinstance->get_alignment();
|
||||
$competencies = $badgeinstance->get_alignments();
|
||||
$relatedbadges = $badgeinstance->get_related_badges();
|
||||
|
||||
if (!$canconfiguredetails) {
|
||||
|
||||
@@ -376,18 +376,40 @@ class provider implements
|
||||
|
||||
// Export the badges.
|
||||
$uniqueid = $DB->sql_concat_join("'-'", ['b.id', 'COALESCE(bc.id, 0)', 'COALESCE(bi.id, 0)',
|
||||
'COALESCE(bma.id, 0)', 'COALESCE(bcm.id, 0)']);
|
||||
'COALESCE(bma.id, 0)', 'COALESCE(bcm.id, 0)', 'COALESCE(brb.id, 0)', 'COALESCE(ba.id, 0)']);
|
||||
$sql = "
|
||||
SELECT $uniqueid AS uniqueid, b.id,
|
||||
bi.id AS biid, bi.dateissued, bi.dateexpire, bi.uniquehash,
|
||||
bma.id AS bmaid, bma.datemet, bma.issuerid,
|
||||
bcm.id AS bcmid,
|
||||
c.fullname AS coursename,
|
||||
be.id AS beid,
|
||||
be.issuername AS beissuername,
|
||||
be.issuerurl AS beissuerurl,
|
||||
be.issueremail AS beissueremail,
|
||||
be.claimid AS beclaimid,
|
||||
be.claimcomment AS beclaimcomment,
|
||||
be.dateissued AS bedateissued,
|
||||
brb.id as rbid,
|
||||
brb.badgeid as rbbadgeid,
|
||||
brb.relatedbadgeid as rbrelatedbadgeid,
|
||||
ba.id as baid,
|
||||
ba.targetname as batargetname,
|
||||
ba.targeturl as batargeturl,
|
||||
ba.targetdescription as batargetdescription,
|
||||
ba.targetframework as batargetframework,
|
||||
ba.targetcode as batargetcode,
|
||||
$ctxfields
|
||||
FROM {badge} b
|
||||
LEFT JOIN {badge_issued} bi
|
||||
ON bi.badgeid = b.id
|
||||
AND bi.userid = :userid1
|
||||
LEFT JOIN {badge_related} brb
|
||||
ON ( b.id = brb.badgeid OR b.id = brb.relatedbadgeid )
|
||||
LEFT JOIN {badge_competencies} ba
|
||||
ON ( b.id = ba.badgeid )
|
||||
LEFT JOIN {badge_endorsement} be
|
||||
ON be.badgeid = b.id
|
||||
LEFT JOIN {badge_manual_award} bma
|
||||
ON bma.badgeid = b.id
|
||||
AND bma.recipientid = :userid2
|
||||
@@ -421,15 +443,32 @@ class provider implements
|
||||
if ($carry === null) {
|
||||
$carry = [
|
||||
'name' => $badge->name,
|
||||
'version' => $badge->version,
|
||||
'language' => $badge->language,
|
||||
'imageauthorname' => $badge->imageauthorname,
|
||||
'imageauthoremail' => $badge->imageauthoremail,
|
||||
'imageauthorurl' => $badge->imageauthorurl,
|
||||
'imagecaption' => $badge->imagecaption,
|
||||
'issued' => null,
|
||||
'manual_award' => null,
|
||||
'criteria_met' => []
|
||||
'criteria_met' => [],
|
||||
'endorsement' => null,
|
||||
];
|
||||
|
||||
if ($badge->type == BADGE_TYPE_COURSE) {
|
||||
context_helper::preload_from_record($record);
|
||||
$carry['course'] = format_string($record->coursename, true, ['context' => $badge->get_context()]);
|
||||
}
|
||||
if (!empty($record->beid)) {
|
||||
$carry['endorsement'] = [
|
||||
'issuername' => $record->beissuername,
|
||||
'issuerurl' => $record->beissuerurl,
|
||||
'issueremail' => $record->beissueremail,
|
||||
'claimid' => $record->beclaimid,
|
||||
'claimcomment' => $record->beclaimcomment,
|
||||
'dateissued' => $record->bedateissued ? transform::datetime($record->bedateissued) : null
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($record->biid)) {
|
||||
$carry['issued'] = [
|
||||
@@ -446,6 +485,52 @@ class provider implements
|
||||
];
|
||||
}
|
||||
}
|
||||
if (!empty($record->rbid)) {
|
||||
if (empty($carry['related_badge'])) {
|
||||
$carry['related_badge'] = [];
|
||||
}
|
||||
$rbid = $record->rbbadgeid;
|
||||
if ($rbid == $record->id) {
|
||||
$rbid = $record->rbrelatedbadgeid;
|
||||
}
|
||||
$exists = false;
|
||||
foreach ($carry['related_badge'] as $related) {
|
||||
if ($related['badgeid'] == $rbid) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$exists) {
|
||||
$relatedbadge = new badge($rbid);
|
||||
$carry['related_badge'][] = [
|
||||
'badgeid' => $rbid,
|
||||
'badgename' => $relatedbadge->name
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($record->baid)) {
|
||||
if (empty($carry['alignment'])) {
|
||||
$carry['alignment'] = [];
|
||||
}
|
||||
$exists = false;
|
||||
$newalignment = [
|
||||
'targetname' => $record->batargetname,
|
||||
'targeturl' => $record->batargeturl,
|
||||
'targetdescription' => $record->batargetdescription,
|
||||
'targetframework' => $record->batargetframework,
|
||||
'targetcode' => $record->batargetcode,
|
||||
];
|
||||
foreach ($carry['alignment'] as $alignment) {
|
||||
if ($alignment == $newalignment) {
|
||||
$exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$exists) {
|
||||
$carry['alignment'][] = $newalignment;
|
||||
}
|
||||
}
|
||||
|
||||
// Export the details of the criteria met.
|
||||
// We only do that once, when we find that a least one criteria was met.
|
||||
|
||||
@@ -97,7 +97,7 @@ if ($alignmentid || $action == 'add' || $action == 'edit') {
|
||||
$urlbutton = new moodle_url('/badges/competency.php', array('id' => $badge->id, 'action' => 'add'));
|
||||
echo $OUTPUT->box($OUTPUT->single_button($urlbutton, get_string('addalignment', 'badges')), 'clearfix mdl-align');
|
||||
}
|
||||
$alignment = $badge->get_alignment();
|
||||
$alignment = $badge->get_alignments();
|
||||
if (count($alignment) > 0) {
|
||||
$renderrelated = new badge_competencies_alignment($alignment, $badgeid);
|
||||
echo $output->render($renderrelated);
|
||||
|
||||
+6
-5
@@ -50,14 +50,15 @@ function badge_review_cron() {
|
||||
if (empty($CFG->badges_allowcoursebadges)) {
|
||||
$coursesql = '';
|
||||
} else {
|
||||
$coursesql = ' OR EXISTS (SELECT id FROM {course} WHERE visible = :visible AND startdate < :current) ';
|
||||
$coursesql = ' OR EXISTS (SELECT c.id FROM {course} c WHERE c.visible = :visible AND c.startdate < :current'
|
||||
. ' AND c.id = b.courseid)';
|
||||
$courseparams = array('visible' => true, 'current' => time());
|
||||
}
|
||||
|
||||
$sql = 'SELECT id
|
||||
FROM {badge}
|
||||
WHERE (status = :active OR status = :activelocked)
|
||||
AND (type = :site ' . $coursesql . ')';
|
||||
$sql = 'SELECT b.id
|
||||
FROM {badge} b
|
||||
WHERE (b.status = :active OR b.status = :activelocked)
|
||||
AND (b.type = :site ' . $coursesql . ')';
|
||||
$badgeparams = array(
|
||||
'active' => BADGE_STATUS_ACTIVE,
|
||||
'activelocked' => BADGE_STATUS_ACTIVE_LOCKED,
|
||||
|
||||
@@ -96,6 +96,7 @@ if (!has_any_capability(array(
|
||||
'moodle/badges:viewawarded',
|
||||
'moodle/badges:createbadge',
|
||||
'moodle/badges:awardbadge',
|
||||
'moodle/badges:configurecriteria',
|
||||
'moodle/badges:configuremessages',
|
||||
'moodle/badges:configuredetails',
|
||||
'moodle/badges:deletebadge'), $PAGE->context)) {
|
||||
|
||||
+2
-2
@@ -428,7 +428,7 @@ class core_badges_renderer extends plugin_renderer_base {
|
||||
}
|
||||
$output .= html_writer::alist($items, array(), 'ul');
|
||||
}
|
||||
$competencies = $badge->get_alignment();
|
||||
$competencies = $badge->get_alignments();
|
||||
if (!empty($competencies)) {
|
||||
$output .= $this->heading(get_string('alignment', 'badges'), 3);
|
||||
$items = array();
|
||||
@@ -1106,7 +1106,7 @@ class core_badges_renderer extends plugin_renderer_base {
|
||||
protected function print_badge_competencies(badge $badge) {
|
||||
$output = '';
|
||||
$output .= $this->heading(get_string('alignment', 'badges'), 3);
|
||||
$competencies = $badge->get_alignment();
|
||||
$competencies = $badge->get_alignments();
|
||||
if (!empty($competencies)) {
|
||||
$items = array();
|
||||
foreach ($competencies as $competency) {
|
||||
|
||||
@@ -813,7 +813,7 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
|
||||
$competency2 = clone $competency1;
|
||||
$newid1 = $badge->save_alignment($competency1);
|
||||
$newid2 = $badge->save_alignment($competency2);
|
||||
$competencies1 = $badge->get_alignment();
|
||||
$competencies1 = $badge->get_alignments();
|
||||
$this->assertCount(2, $competencies1);
|
||||
|
||||
$this->assertEquals($competency1->badgeid, $competencies1[$newid1]->badgeid);
|
||||
@@ -826,12 +826,12 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
|
||||
// Update competency aligment.
|
||||
$competencies1[$newid1]->targetname = 'CCSS.ELA-Literacy.RST.11-12.3 update';
|
||||
$badge->save_alignment($competencies1[$newid1], $competencies1[$newid1]->id);
|
||||
$competencies2 = $badge->get_alignment();
|
||||
$competencies2 = $badge->get_alignments();
|
||||
$this->assertEquals($competencies1[$newid1]->id, $competencies2[$newid1]->id);
|
||||
$this->assertEquals($competencies1[$newid1]->targetname, $competencies2[$newid1]->targetname);
|
||||
|
||||
// Delete competency alignment.
|
||||
$badge->delete_alignment($competencies1[$newid2]->id);
|
||||
$this->assertCount(1, $badge->get_alignment());
|
||||
$this->assertCount(1, $badge->get_alignments());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ class core_badges_external_testcase extends externallib_advanced_testcase {
|
||||
// Get the endorsement, competencies and related badges.
|
||||
$badgeinstance = new badge($badge->id);
|
||||
$endorsement = $badgeinstance->get_endorsement();
|
||||
$competencies = $badgeinstance->get_alignment();
|
||||
$competencies = $badgeinstance->get_alignments();
|
||||
$relatedbadges = $badgeinstance->get_related_badges();
|
||||
$badge->competencies = array();
|
||||
$badge->relatedbadges = array();
|
||||
|
||||
@@ -306,7 +306,11 @@ class core_badges_privacy_testcase extends provider_testcase {
|
||||
$u2ctx = context_user::instance($u2->id);
|
||||
|
||||
$b1 = $this->create_badge(['usercreated' => $u3->id]);
|
||||
$this->endorse_badge(['badgeid' => $b1->id]);
|
||||
$this->align_badge(['badgeid' => $b1->id], ' (1)');
|
||||
$this->align_badge(['badgeid' => $b1->id], ' (2)');
|
||||
$b2 = $this->create_badge(['type' => BADGE_TYPE_COURSE, 'courseid' => $c1->id, 'usermodified' => $u3->id]);
|
||||
$this->relate_badge($b1->id, $b2->id);
|
||||
$b3 = $this->create_badge();
|
||||
$b3crit = $this->create_criteria_manual($b3->id);
|
||||
$b4 = $this->create_badge();
|
||||
@@ -333,6 +337,12 @@ class core_badges_privacy_testcase extends provider_testcase {
|
||||
$path = [get_string('badges', 'core_badges'), "{$b1->name} ({$b1->id})"];
|
||||
$data = writer::with_context($u1ctx)->get_data($path);
|
||||
$this->assertEquals($b1->name, $data->name);
|
||||
$this->assertEquals($b1->version, $data->version);
|
||||
$this->assertEquals($b1->language, $data->language);
|
||||
$this->assertEquals($b1->imageauthorname, $data->imageauthorname);
|
||||
$this->assertEquals($b1->imageauthoremail, $data->imageauthoremail);
|
||||
$this->assertEquals($b1->imageauthorurl, $data->imageauthorurl);
|
||||
$this->assertEquals($b1->imagecaption, $data->imagecaption);
|
||||
$this->assertNotEmpty($data->issued);
|
||||
$this->assertEmpty($data->manual_award);
|
||||
$this->assertEmpty($data->criteria_met);
|
||||
@@ -340,6 +350,33 @@ class core_badges_privacy_testcase extends provider_testcase {
|
||||
$this->assertEquals('yoohoo', $data->issued['unique_hash']);
|
||||
$this->assertNull($data->issued['expires_on']);
|
||||
|
||||
$this->assertNotEmpty($data->endorsement);
|
||||
$this->assertNotEmpty($data->endorsement['issuername']);
|
||||
$this->assertNotEmpty($data->endorsement['issuerurl']);
|
||||
$this->assertNotEmpty($data->endorsement['issueremail']);
|
||||
$this->assertNotEmpty($data->endorsement['claimid']);
|
||||
$this->assertNotEmpty($data->endorsement['claimcomment']);
|
||||
$this->assertNotEmpty($data->endorsement['dateissued']);
|
||||
|
||||
$this->assertNotEmpty($data->related_badge);
|
||||
$this->assertNotEmpty($data->related_badge[0]);
|
||||
$this->assertEquals($data->related_badge[0]['badgeid'], $b2->id);
|
||||
$this->assertEquals($data->related_badge[0]['badgename'], $b2->name);
|
||||
|
||||
$this->assertNotEmpty($data->alignment);
|
||||
$this->assertNotEmpty($data->alignment[0]);
|
||||
$this->assertNotEmpty($data->alignment[0]['targetname']);
|
||||
$this->assertNotEmpty($data->alignment[0]['targeturl']);
|
||||
$this->assertNotEmpty($data->alignment[0]['targetdescription']);
|
||||
$this->assertNotEmpty($data->alignment[0]['targetframework']);
|
||||
$this->assertNotEmpty($data->alignment[0]['targetcode']);
|
||||
$this->assertNotEmpty($data->alignment[1]);
|
||||
$this->assertNotEmpty($data->alignment[1]['targetname']);
|
||||
$this->assertNotEmpty($data->alignment[1]['targeturl']);
|
||||
$this->assertNotEmpty($data->alignment[1]['targetdescription']);
|
||||
$this->assertNotEmpty($data->alignment[1]['targetframework']);
|
||||
$this->assertNotEmpty($data->alignment[1]['targetcode']);
|
||||
|
||||
$path = [get_string('badges', 'core_badges'), "{$b2->name} ({$b2->id})"];
|
||||
$data = writer::with_context($u1ctx)->get_data($path);
|
||||
$this->assertEquals($b2->name, $data->name);
|
||||
@@ -598,12 +635,79 @@ class core_badges_privacy_testcase extends provider_testcase {
|
||||
'attachment' => 1,
|
||||
'notification' => 0,
|
||||
'status' => BADGE_STATUS_ACTIVE,
|
||||
'version' => OPEN_BADGES_V2,
|
||||
'language' => 'en',
|
||||
'imageauthorname' => 'Image author',
|
||||
'imageauthoremail' => 'author@example.com',
|
||||
'imageauthorurl' => 'http://image.example.com/',
|
||||
'imagecaption' => 'Image caption'
|
||||
], $params);
|
||||
$record->id = $DB->insert_record('badge', $record);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Relate a badge.
|
||||
*
|
||||
* @param int $badgeid The badge ID.
|
||||
* @param int $relatedbadgeid The related badge ID.
|
||||
* @return object
|
||||
*/
|
||||
protected function relate_badge(int $badgeid, int $relatedbadgeid) {
|
||||
global $DB;
|
||||
$record = (object) [
|
||||
'badgeid' => $badgeid,
|
||||
'relatedbadgeid' => $relatedbadgeid
|
||||
];
|
||||
$record->id = $DB->insert_record('badge_related', $record);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Align a badge.
|
||||
*
|
||||
* @param array $params Parameters.
|
||||
* @param string $suffix Option to append to strings.
|
||||
* @return object
|
||||
*/
|
||||
protected function align_badge(array $params = [], $suffix = '') {
|
||||
global $DB;
|
||||
$record = (object) array_merge([
|
||||
'badgeid' => null,
|
||||
'targetname' => "Alignment name" . $suffix,
|
||||
'targeturl' => "http://issuer-url.domain.co.nz",
|
||||
'targetdescription' => "Description" . $suffix,
|
||||
'targetframework' => "Framework" . $suffix,
|
||||
'targetcode' => "Code" . $suffix
|
||||
], $params);
|
||||
$record->id = $DB->insert_record('badge_competencies', $record);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Endorse a badge.
|
||||
*
|
||||
* @param array $params Parameters.
|
||||
* @return object
|
||||
*/
|
||||
protected function endorse_badge(array $params = []) {
|
||||
global $DB, $USER;
|
||||
$record = (object) array_merge([
|
||||
'issuername' => "External issuer name",
|
||||
'issuerurl' => "http://issuer-url.domain.co.nz",
|
||||
'issueremail' => "issuer@example.com",
|
||||
'claimid' => "Claim ID",
|
||||
'claimcomment' => "Claim comment",
|
||||
'dateissued' => time()
|
||||
], $params);
|
||||
$record->id = $DB->insert_record('badge_endorsement', $record);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a backpack.
|
||||
*
|
||||
|
||||
@@ -429,13 +429,13 @@ class block_base {
|
||||
function html_attributes() {
|
||||
$attributes = array(
|
||||
'id' => 'inst' . $this->instance->id,
|
||||
'class' => 'block_' . $this->name(). ' block',
|
||||
'class' => 'block_' . $this->name() . ' block',
|
||||
'role' => $this->get_aria_role()
|
||||
);
|
||||
if ($this->hide_header()) {
|
||||
$attributes['class'] .= ' no-header';
|
||||
}
|
||||
if ($this->instance_can_be_docked() && get_user_preferences('docked_block_instance_'.$this->instance->id, 0)) {
|
||||
if ($this->instance_can_be_docked() && get_user_preferences('docked_block_instance_' . $this->instance->id, 0)) {
|
||||
$attributes['class'] .= ' dock_on_load';
|
||||
}
|
||||
return $attributes;
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -499,7 +499,7 @@ function(
|
||||
}
|
||||
|
||||
// Set the last page to either the current or next page
|
||||
if (loadedPages[currentPage].courses.length < pageData.limit) {
|
||||
if (loadedPages[currentPage].courses.length < pageData.limit || !remainingCourses.length) {
|
||||
lastPage = currentPage;
|
||||
actions.allItemsLoaded(currentPage);
|
||||
} else if (loadedPages[currentPage + 1] != undefined
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
$string['everything'] = 'Everything';
|
||||
$string['courses'] = 'Categories and courses';
|
||||
$string['coursestructures'] = 'Categories, courses, and course structures';
|
||||
$string['courseactivities'] = 'Categories, courses, and course Activities';
|
||||
$string['courseactivities'] = 'Categories, courses, and course activities';
|
||||
$string['enabledockdesc'] = 'Allow the user to dock this block';
|
||||
$string['expansionlimit'] = 'Generate navigation for the following';
|
||||
$string['linkcategoriesdesc'] = 'Display categories as links';
|
||||
|
||||
@@ -23,20 +23,27 @@
|
||||
{}
|
||||
}}
|
||||
<div data-region="day-filter" class="dropdown">
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
||||
aria-label="{{#str}} ariadayfilter, block_timeline {{/str}}" aria-controls="menudayfilter">
|
||||
{{#pix}} i/duration {{/pix}}
|
||||
<span class="sr-only">
|
||||
{{#str}} ariadayfilter, block_timeline {{/str}}
|
||||
<span data-active-item-text>{{#str}} next30days, block_timeline {{/str}}</span>
|
||||
<span class="sr-only" data-active-item-text>
|
||||
{{#all}} {{#str}} all, core {{/str}} {{/all}}
|
||||
{{#overdue}} {{#str}} overdue, block_timeline {{/str}} {{/overdue}}
|
||||
{{#next7days}} {{#str}}next7days, block_timeline {{/str}} {{/next7days}}
|
||||
{{#next30days}} {{#str}}next30days, block_timeline {{/str}} {{/next30days}}
|
||||
{{#next3months}} {{#str}}next3months, block_timeline {{/str}} {{/next3months}}
|
||||
{{#next6months}} {{#str}}next6months, block_timeline {{/str}} {{/next6months}}
|
||||
</span>
|
||||
</button>
|
||||
<div role="menu" class="dropdown-menu" data-show-active-item>
|
||||
<div id="menudayfilter" role="menu" class="dropdown-menu" data-show-active-item>
|
||||
<a
|
||||
class="dropdown-item {{#all}} active {{/all}}"
|
||||
href="#"
|
||||
data-from="-14"
|
||||
data-filtername="all"
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} all, core {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
{{#all}}aria-current="true"{{/all}}
|
||||
>
|
||||
{{#str}} all, core {{/str}}
|
||||
</a>
|
||||
@@ -47,10 +54,12 @@
|
||||
data-to="0"
|
||||
data-filtername="overdue"
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} overdue, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
{{#overdue}}aria-current="true"{{/overdue}}
|
||||
>
|
||||
{{#str}} overdue, block_timeline {{/str}}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<div class="dropdown-divider" role="separator"></div>
|
||||
<h6 class="dropdown-header">{{#str}} duedate, block_timeline {{/str}}</h6>
|
||||
<a
|
||||
class="dropdown-item {{#next7days}} active {{/next7days}}"
|
||||
@@ -59,6 +68,8 @@
|
||||
data-to="7"
|
||||
data-filtername="next7days"
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next7days, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
{{#next7days}}aria-current="true"{{/next7days}}
|
||||
>
|
||||
{{#str}} next7days, block_timeline {{/str}}
|
||||
</a>
|
||||
@@ -69,6 +80,8 @@
|
||||
data-to="30"
|
||||
data-filtername="next30days"
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next30days, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
{{#next30days}}aria-current="true"{{/next30days}}
|
||||
>
|
||||
{{#str}} next30days, block_timeline {{/str}}
|
||||
</a>
|
||||
@@ -79,6 +92,8 @@
|
||||
data-to="90"
|
||||
data-filtername="next3months"
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next3months, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
{{#next3months}}aria-current="true"{{/next3months}}
|
||||
>
|
||||
{{#str}} next3months, block_timeline {{/str}}
|
||||
</a>
|
||||
@@ -89,6 +104,8 @@
|
||||
data-to="180"
|
||||
data-filtername="next6months"
|
||||
aria-label="{{#str}} ariadayfilteroption, block_timeline, {{#str}} next6months, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
{{#next6months}}aria-current="true"{{/next6months}}
|
||||
>
|
||||
{{#str}} next6months, block_timeline {{/str}}
|
||||
</a>
|
||||
|
||||
@@ -23,20 +23,23 @@
|
||||
{}
|
||||
}}
|
||||
<div data-region="view-selector" class="btn-group">
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle icon-no-margin" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
||||
aria-label="{{#str}} ariaviewselector, block_timeline{{/str}}" aria-controls="menusortby">
|
||||
{{#pix}} i/filter {{/pix}}
|
||||
<span class="sr-only">
|
||||
{{#sorttimelinecourses}}<span data-active-item-text>{{/sorttimelinecourses}}{{#str}} ariaviewselector, block_timeline{{/str}}{{#sorttimelinecourses}}</span>{{/sorttimelinecourses}}
|
||||
{{#sorttimelinedates}}<span data-active-item-text>{{/sorttimelinedates}}{{#str}} sortbydates, block_timeline {{/str}}{{#sorttimelinedates}}</span>{{/sorttimelinedates}}
|
||||
<span class="sr-only" data-active-item-text>
|
||||
{{#sorttimelinecourses}}{{#str}} sortbycourses, block_timeline{{/str}}{{/sorttimelinecourses}}
|
||||
{{#sorttimelinedates}}{{#str}} sortbydates, block_timeline {{/str}}{{/sorttimelinedates}}
|
||||
</span>
|
||||
</button>
|
||||
<div role="menu" class="dropdown-menu dropdown-menu-right list-group hidden" data-show-active-item data-skip-active-class="true" >
|
||||
<div id="menusortby" role="menu" class="dropdown-menu dropdown-menu-right list-group hidden" data-show-active-item data-skip-active-class="true">
|
||||
<a
|
||||
class="dropdown-item {{#sorttimelinedates}}active{{/sorttimelinedates}}"
|
||||
href="#view_dates_{{uniqid}}"
|
||||
data-toggle="tab"
|
||||
data-filtername="sortbydates"
|
||||
aria-label="{{#str}} ariaviewselectoroption, block_timeline, {{#str}} sortbydates, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
{{#sorttimelinedates}}aria-current="true"{{/sorttimelinedates}}
|
||||
>
|
||||
{{#str}} sortbydates, block_timeline {{/str}}
|
||||
</a>
|
||||
@@ -46,6 +49,8 @@
|
||||
data-toggle="tab"
|
||||
data-filtername="sortbycourses"
|
||||
aria-label="{{#str}} ariaviewselectoroption, block_timeline, {{#str}} sortbycourses, block_timeline {{/str}}{{/str}}"
|
||||
role="menuitem"
|
||||
{{#sorttimelinecourses}}aria-current="true"{{/sorttimelinecourses}}
|
||||
>
|
||||
{{#str}} sortbycourses, block_timeline {{/str}}
|
||||
</a>
|
||||
|
||||
@@ -36,7 +36,7 @@ Feature: The timeline block allows users to see upcoming courses
|
||||
|
||||
Scenario: Next 30 days in course view
|
||||
Given I log in as "student1"
|
||||
And I click on "Sort" "button" in the "Timeline" "block"
|
||||
And I click on "Sort timeline items" "button" in the "Timeline" "block"
|
||||
When I click on "Sort by courses" "link" in the "Timeline" "block"
|
||||
Then I should see "Course 1" in the "Timeline" "block"
|
||||
And I should see "Course 2" in the "Timeline" "block"
|
||||
@@ -52,9 +52,9 @@ Feature: The timeline block allows users to see upcoming courses
|
||||
|
||||
Scenario: All in course view
|
||||
Given I log in as "student1"
|
||||
And I click on "Next 30 days" "button" in the "Timeline" "block"
|
||||
And I click on "Filter timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "All" "link" in the "Timeline" "block"
|
||||
And I click on "Sort" "button" in the "Timeline" "block"
|
||||
And I click on "Sort timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "Sort by courses" "link" in the "Timeline" "block"
|
||||
When I click on "More courses" "button" in the "Timeline" "block"
|
||||
Then I should see "Course 3" in the "Timeline" "block"
|
||||
@@ -73,9 +73,9 @@ Feature: The timeline block allows users to see upcoming courses
|
||||
|
||||
Scenario: Persistent sort filter
|
||||
Given I log in as "student1"
|
||||
And I click on "Sort" "button" in the "Timeline" "block"
|
||||
And I click on "Sort timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "Sort by dates" "link" in the "Timeline" "block"
|
||||
And I click on "Sort" "button" in the "Timeline" "block"
|
||||
And I click on "Sort timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "Sort by courses" "link" in the "Timeline" "block"
|
||||
And I reload the page
|
||||
Then I should see "Course 1" in the "Timeline" "block"
|
||||
|
||||
@@ -33,7 +33,7 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
|
||||
Scenario: Next 7 days in date view
|
||||
Given I log in as "student1"
|
||||
And I click on "Next 30 days" "button" in the "Timeline" "block"
|
||||
And I click on "Filter timeline items" "button" in the "Timeline" "block"
|
||||
When I click on "Next 7 days" "link" in the "Timeline" "block"
|
||||
Then I should see "Test choice 1 closes" in the "Timeline" "block"
|
||||
And I should see "Test feedback 1 closes" in the "Timeline" "block"
|
||||
@@ -44,7 +44,7 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
|
||||
Scenario: Overdue in date view
|
||||
Given I log in as "student1"
|
||||
And I click on "Next 30 days" "button" in the "Timeline" "block"
|
||||
And I click on "Filter timeline items" "button" in the "Timeline" "block"
|
||||
When I click on "Overdue" "link" in the "Timeline" "block"
|
||||
Then I should see "Test assign 1 is due" in the "Timeline" "block"
|
||||
And I should not see "Test choice 2 closes" in the "Timeline" "block"
|
||||
@@ -55,7 +55,7 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
|
||||
Scenario: All in date view
|
||||
Given I log in as "student1"
|
||||
And I click on "Next 30 days" "button" in the "Timeline" "block"
|
||||
And I click on "Filter timeline items" "button" in the "Timeline" "block"
|
||||
When I click on "All" "link" in the "Timeline" "block"
|
||||
Then I should see "Test assign 1 is due" in the "Timeline" "block"
|
||||
And I should see "Test feedback 1 closes" in the "Timeline" "block"
|
||||
@@ -75,7 +75,7 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
|
||||
Scenario: All in date view no next
|
||||
Given I log in as "student1"
|
||||
And I click on "Next 30 days" "button" in the "Timeline" "block"
|
||||
And I click on "Filter timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "All" "link" in the "Timeline" "block"
|
||||
And I click on "5" "button" in the "Timeline" "block"
|
||||
When I click on "25" "link" in the "Timeline" "block"
|
||||
@@ -89,7 +89,7 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
|
||||
Scenario: Persistent All in date view
|
||||
Given I log in as "student1"
|
||||
And I click on "Next 30 days" "button" in the "Timeline" "block"
|
||||
And I click on "Filter timeline items" "button" in the "Timeline" "block"
|
||||
When I click on "All" "link" in the "Timeline" "block"
|
||||
And I reload the page
|
||||
Then I should see "Test assign 1 is due" in the "Timeline" "block"
|
||||
@@ -110,7 +110,7 @@ Feature: The timeline block allows users to see upcoming activities
|
||||
|
||||
Scenario: Persistent Overdue in date view
|
||||
Given I log in as "student1"
|
||||
And I click on "Next 30 days" "button" in the "Timeline" "block"
|
||||
And I click on "Filter timeline items" "button" in the "Timeline" "block"
|
||||
When I click on "Overdue" "link" in the "Timeline" "block"
|
||||
And I reload the page
|
||||
Then I should see "Test assign 1 is due" in the "Timeline" "block"
|
||||
|
||||
@@ -35,9 +35,9 @@ Feature: The timeline block allows user persistence of their page limits
|
||||
|
||||
Scenario: Toggle the page limit 5 - 25
|
||||
Given I log in as "student1"
|
||||
And I click on "Next 30 days" "button" in the "Timeline" "block"
|
||||
And I click on "Filter timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "All" "link" in the "Timeline" "block"
|
||||
And I click on "Sort" "button" in the "Timeline" "block"
|
||||
And I click on "Sort timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "Sort by dates" "link" in the "Timeline" "block"
|
||||
When I click on "5" "button" in the "Timeline" "block"
|
||||
And I click on "25" "link"
|
||||
@@ -48,9 +48,9 @@ Feature: The timeline block allows user persistence of their page limits
|
||||
|
||||
Scenario: Toggle the page limit 25 - 5
|
||||
Given I log in as "student1"
|
||||
And I click on "Next 30 days" "button" in the "Timeline" "block"
|
||||
And I click on "Filter timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "All" "link" in the "Timeline" "block"
|
||||
And I click on "Sort" "button" in the "Timeline" "block"
|
||||
And I click on "Sort timeline items" "button" in the "Timeline" "block"
|
||||
And I click on "Sort by dates" "link" in the "Timeline" "block"
|
||||
When I click on "5" "button" in the "Timeline" "block"
|
||||
And I click on "25" "link"
|
||||
|
||||
Vendored
+7
@@ -1284,6 +1284,13 @@ class cache implements cache_loader {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses may support purging cache of all data belonging to the
|
||||
* current user.
|
||||
*/
|
||||
public function purge_current_user() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,7 @@ class core_calendar_export_form extends moodleform {
|
||||
|
||||
$export = array();
|
||||
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsall', 'calendar'), 'all');
|
||||
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtocategories', 'calendar'), 'categories');
|
||||
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtocourses', 'calendar'), 'courses');
|
||||
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventsrelatedtogroups', 'calendar'), 'groups');
|
||||
$export[] = $mform->createElement('radio', 'exportevents', '', get_string('eventspersonal', 'calendar'), 'user');
|
||||
|
||||
+1
-1
@@ -125,7 +125,7 @@ class event_exporter_base extends exporter {
|
||||
'null' => NULL_ALLOWED
|
||||
],
|
||||
'location' => [
|
||||
'type' => PARAM_RAW_TRIMMED,
|
||||
'type' => PARAM_RAW,
|
||||
'optional' => true,
|
||||
'default' => null,
|
||||
'null' => NULL_ALLOWED
|
||||
|
||||
@@ -532,8 +532,7 @@ class provider implements
|
||||
AND ctx.contextlevel = :categorycontext)
|
||||
OR (e.courseid = ctx.instanceid
|
||||
AND (e.eventtype = 'course'
|
||||
OR e.eventtype = 'group'
|
||||
OR e.modulename != '0')
|
||||
OR e.eventtype = 'group')
|
||||
AND ctx.contextlevel = :coursecontext)
|
||||
OR (e.userid = ctx.instanceid
|
||||
AND e.eventtype = 'user'
|
||||
|
||||
@@ -40,8 +40,8 @@ $time = optional_param('preset_time', 'weeknow', PARAM_ALPHA);
|
||||
$now = $calendartype->timestamp_to_date_array(time());
|
||||
|
||||
// Let's see if we have sufficient and correct data
|
||||
$allowed_what = array('all', 'user', 'groups', 'courses');
|
||||
$allowed_time = array('weeknow', 'weeknext', 'monthnow', 'monthnext', 'recentupcoming', 'custom');
|
||||
$allowedwhat = ['all', 'user', 'groups', 'courses', 'categories'];
|
||||
$allowedtime = ['weeknow', 'weeknext', 'monthnow', 'monthnext', 'recentupcoming', 'custom'];
|
||||
|
||||
if (!empty($generateurl)) {
|
||||
$authtoken = sha1($user->id . $user->password . $CFG->calendar_exportsalt);
|
||||
@@ -56,9 +56,9 @@ if (!empty($generateurl)) {
|
||||
redirect($link->out());
|
||||
die;
|
||||
}
|
||||
|
||||
$paramcategory = false;
|
||||
if(!empty($what) && !empty($time)) {
|
||||
if(in_array($what, $allowed_what) && in_array($time, $allowed_time)) {
|
||||
if(in_array($what, $allowedwhat) && in_array($time, $allowedtime)) {
|
||||
$courses = enrol_get_users_courses($user->id, true, 'id, visible, shortname');
|
||||
// Array of courses that we will pass to calendar_get_legacy_events() which
|
||||
// is initially set to the list of the user's courses.
|
||||
@@ -78,6 +78,7 @@ if(!empty($what) && !empty($time)) {
|
||||
$courses[SITEID] = new stdClass;
|
||||
$courses[SITEID]->shortname = get_string('globalevents', 'calendar');
|
||||
$paramcourses[SITEID] = $courses[SITEID];
|
||||
$paramcategory = true;
|
||||
} else if ($what == 'groups') {
|
||||
$users = false;
|
||||
$paramcourses = array();
|
||||
@@ -85,6 +86,11 @@ if(!empty($what) && !empty($time)) {
|
||||
$users = $user->id;
|
||||
$groups = false;
|
||||
$paramcourses = array();
|
||||
} else if ($what == 'categories') {
|
||||
$users = $user->id;
|
||||
$groups = false;
|
||||
$paramcourses = array();
|
||||
$paramcategory = true;
|
||||
} else {
|
||||
$users = false;
|
||||
$groups = false;
|
||||
@@ -180,7 +186,9 @@ if(!empty($what) && !empty($time)) {
|
||||
die();
|
||||
}
|
||||
}
|
||||
$events = calendar_get_legacy_events($timestart, $timeend, $users, $groups, array_keys($paramcourses), false);
|
||||
|
||||
$events = calendar_get_legacy_events($timestart, $timeend, $users, $groups, array_keys($paramcourses), false, true,
|
||||
$paramcategory);
|
||||
|
||||
$ical = new iCalendar;
|
||||
$ical->add_property('method', 'PUBLISH');
|
||||
|
||||
+36
-41
@@ -608,49 +608,44 @@ class calendar_event {
|
||||
$updaterepeated = (!empty($this->properties->repeatid) && !empty($this->properties->repeateditall));
|
||||
|
||||
if ($updaterepeated) {
|
||||
// Update all.
|
||||
|
||||
$sqlset = 'name = ?,
|
||||
description = ?,
|
||||
timeduration = ?,
|
||||
timemodified = ?,
|
||||
groupid = ?,
|
||||
courseid = ?';
|
||||
|
||||
// Note: Group and course id may not be set. If not, keep their current values.
|
||||
$params = [
|
||||
$this->properties->name,
|
||||
$this->properties->description,
|
||||
$this->properties->timeduration,
|
||||
time(),
|
||||
isset($this->properties->groupid) ? $this->properties->groupid : $event->groupid,
|
||||
isset($this->properties->courseid) ? $this->properties->courseid : $event->courseid,
|
||||
];
|
||||
|
||||
// Note: Only update start date, if it was changed by the user.
|
||||
if ($this->properties->timestart != $event->timestart) {
|
||||
$timestartoffset = $this->properties->timestart - $event->timestart;
|
||||
$sql = "UPDATE {event}
|
||||
SET name = ?,
|
||||
description = ?,
|
||||
timestart = timestart + ?,
|
||||
timeduration = ?,
|
||||
timemodified = ?,
|
||||
groupid = ?,
|
||||
courseid = ?
|
||||
WHERE repeatid = ?";
|
||||
// Note: Group and course id may not be set. If not, keep their current values.
|
||||
$params = [
|
||||
$this->properties->name,
|
||||
$this->properties->description,
|
||||
$timestartoffset,
|
||||
$this->properties->timeduration,
|
||||
time(),
|
||||
isset($this->properties->groupid) ? $this->properties->groupid : $event->groupid,
|
||||
isset($this->properties->courseid) ? $this->properties->courseid : $event->courseid,
|
||||
$event->repeatid
|
||||
];
|
||||
} else {
|
||||
$sql = "UPDATE {event}
|
||||
SET name = ?,
|
||||
description = ?,
|
||||
timeduration = ?,
|
||||
timemodified = ?,
|
||||
groupid = ?,
|
||||
courseid = ?
|
||||
WHERE repeatid = ?";
|
||||
// Note: Group and course id may not be set. If not, keep their current values.
|
||||
$params = [
|
||||
$this->properties->name,
|
||||
$this->properties->description,
|
||||
$this->properties->timeduration,
|
||||
time(),
|
||||
isset($this->properties->groupid) ? $this->properties->groupid : $event->groupid,
|
||||
isset($this->properties->courseid) ? $this->properties->courseid : $event->courseid,
|
||||
$event->repeatid
|
||||
];
|
||||
$sqlset .= ', timestart = timestart + ?';
|
||||
$params[] = $timestartoffset;
|
||||
}
|
||||
|
||||
// Note: Only update location, if it was changed by the user.
|
||||
$updatelocation = (!empty($this->properties->location) && $this->properties->location !== $event->location);
|
||||
if ($updatelocation) {
|
||||
$sqlset .= ', location = ?';
|
||||
$params[] = $this->properties->location;
|
||||
}
|
||||
|
||||
// Update all.
|
||||
$sql = "UPDATE {event}
|
||||
SET $sqlset
|
||||
WHERE repeatid = ?";
|
||||
|
||||
$params[] = $event->repeatid;
|
||||
$DB->execute($sql, $params);
|
||||
|
||||
// Trigger an update event for each of the calendar event.
|
||||
@@ -2851,7 +2846,7 @@ function calendar_add_icalendar_event($event, $unused = null, $subscriptionid, $
|
||||
}
|
||||
|
||||
$eventrecord->location = empty($event->properties['LOCATION'][0]->value) ? '' :
|
||||
str_replace('\\', '', $event->properties['LOCATION'][0]->value);
|
||||
trim(str_replace('\\', '', $event->properties['LOCATION'][0]->value));
|
||||
$eventrecord->uuid = $event->properties['UID'][0]->value;
|
||||
$eventrecord->timemodified = time();
|
||||
|
||||
|
||||
@@ -48,6 +48,14 @@ Feature: Export calendar events
|
||||
When I click on "Get calendar URL" "button"
|
||||
Then I should see "&preset_what=groups&"
|
||||
|
||||
Scenario: Generating calendar URL for category events
|
||||
Given I follow "This month"
|
||||
And I click on "Export calendar" "button"
|
||||
And I set the field "Events related to categories" to "1"
|
||||
And I set the field "Recent and next 60 days" to "1"
|
||||
When I click on "Get calendar URL" "button"
|
||||
Then I should see "&preset_what=categories&"
|
||||
|
||||
Scenario: Generating calendar URL for user events
|
||||
Given I follow "This month"
|
||||
And I click on "Export calendar" "button"
|
||||
|
||||
@@ -365,12 +365,23 @@ class core_calendar_privacy_testcase extends provider_testcase {
|
||||
// Delete all Calendar Events for all Users by Context for Course 2.
|
||||
provider::delete_data_for_all_users_in_context($course2context);
|
||||
|
||||
// Verify all Calendar Events for Course 2 were deleted.
|
||||
$events = $DB->get_records('event', array('courseid' => $course2->id));
|
||||
// Verify all Calendar Events for Course 2 context were deleted.
|
||||
$events = $DB->get_records('event', array('courseid' => $course2->id, 'modulename' => '0'));
|
||||
$this->assertCount(0, $events);
|
||||
// Verify all Calendar Subscriptions for Course 2 were deleted.
|
||||
$subscriptions = $DB->get_records('event_subscriptions', array('courseid' => $course2->id));
|
||||
$this->assertCount(0, $subscriptions);
|
||||
|
||||
// Verify all Calendar Events for the assignment exists still.
|
||||
$events = $DB->get_records('event', array('modulename' => 'assign'));
|
||||
$this->assertCount(2, $events);
|
||||
|
||||
// Delete all Calendar Events for all Users by Context for the assignment.
|
||||
provider::delete_data_for_all_users_in_context($modulecontext);
|
||||
|
||||
// Verify all Calendar Events for the assignment context were deleted.
|
||||
$events = $DB->get_records('event', array('modulename' => 'assign'));
|
||||
$this->assertCount(0, $events);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-7
@@ -28,7 +28,6 @@ require_once($CFG->dirroot.'/cohort/upload_form.php');
|
||||
require_once($CFG->libdir . '/csvlib.class.php');
|
||||
|
||||
$contextid = optional_param('contextid', 0, PARAM_INT);
|
||||
$returnurl = optional_param('returnurl', '', PARAM_URL);
|
||||
|
||||
require_login();
|
||||
|
||||
@@ -56,13 +55,9 @@ if ($context->contextlevel == CONTEXT_COURSECAT) {
|
||||
navigation_node::override_active_url(new moodle_url('/cohort/index.php', array()));
|
||||
}
|
||||
|
||||
$uploadform = new cohort_upload_form(null, array('contextid' => $context->id, 'returnurl' => $returnurl));
|
||||
$uploadform = new cohort_upload_form(null, array('contextid' => $context->id));
|
||||
|
||||
if ($returnurl) {
|
||||
$returnurl = new moodle_url($returnurl);
|
||||
} else {
|
||||
$returnurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
|
||||
}
|
||||
$returnurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
|
||||
|
||||
if ($uploadform->is_cancelled()) {
|
||||
redirect($returnurl);
|
||||
|
||||
@@ -48,9 +48,6 @@ class cohort_upload_form extends moodleform {
|
||||
$mform = $this->_form;
|
||||
$data = (object)$this->_customdata;
|
||||
|
||||
$mform->addElement('hidden', 'returnurl');
|
||||
$mform->setType('returnurl', PARAM_URL);
|
||||
|
||||
$mform->addElement('header', 'cohortfileuploadform', get_string('uploadafile'));
|
||||
|
||||
$filepickeroptions = array();
|
||||
|
||||
@@ -192,8 +192,8 @@ class course_module_competency extends persistent {
|
||||
$sql = 'SELECT COUNT(comp.id)
|
||||
FROM {' . self::TABLE . '} coursemodulecomp
|
||||
JOIN {' . competency::TABLE . '} comp
|
||||
ON coursecomp.competencyid = comp.id
|
||||
WHERE coursecomp.cmid = ? ';
|
||||
ON coursemodulecomp.competencyid = comp.id
|
||||
WHERE coursemodulecomp.cmid = ? ';
|
||||
$params = array($cmid);
|
||||
|
||||
$results = $DB->count_records_sql($sql, $params);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Course module competency persistent class tests.
|
||||
*
|
||||
* @package core_competency
|
||||
* @copyright 2019 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
use core_competency\course_module_competency;
|
||||
|
||||
/**
|
||||
* Course module competency persistent testcase.
|
||||
*
|
||||
* @package core_competency
|
||||
* @copyright 2019 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_competency_course_module_competency_testcase extends advanced_testcase {
|
||||
|
||||
public function test_count_competencies() {
|
||||
global $CFG, $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$dg = $this->getDataGenerator();
|
||||
$lpg = $dg->get_plugin_generator('core_competency');
|
||||
|
||||
$c1 = $dg->create_course();
|
||||
$u1 = $dg->create_user();
|
||||
$u2 = $dg->create_user();
|
||||
|
||||
$framework = $lpg->create_framework();
|
||||
$comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // In C1, and C2.
|
||||
$comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // In C2.
|
||||
$lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c1->id));
|
||||
$lpg->create_course_competency(array('competencyid' => $comp2->get('id'), 'courseid' => $c1->id));
|
||||
|
||||
$assign1a = $dg->create_module('assign', ['course' => $c1]);
|
||||
$assign1b = $dg->create_module('assign', ['course' => $c1]);
|
||||
$cmc1a = $lpg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1a->cmid]);
|
||||
$cmc1b = $lpg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1b->cmid]);
|
||||
$cmc2b = $lpg->create_course_module_competency(['competencyid' => $comp2->get('id'), 'cmid' => $assign1b->cmid]);
|
||||
|
||||
// Enrol the user 1 in C1.
|
||||
$dg->enrol_user($u1->id, $c1->id);
|
||||
|
||||
$all = course_module_competency::list_course_module_competencies($assign1a->cmid);
|
||||
$this->assertEquals(course_module_competency::count_competencies($assign1a->cmid), count($all));
|
||||
|
||||
$all = course_module_competency::list_course_module_competencies($assign1b->cmid);
|
||||
$this->assertEquals(course_module_competency::count_competencies($assign1b->cmid), count($all));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -60,6 +60,10 @@ class progress {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$completion->is_tracked_user($userid)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Before we check how many modules have been completed see if the course has.
|
||||
if ($completion->is_course_complete($userid)) {
|
||||
return 100;
|
||||
|
||||
@@ -151,4 +151,33 @@ class core_completion_progress_testcase extends advanced_testcase {
|
||||
// Check that the result was null.
|
||||
$this->assertNull(\core_completion\progress::get_course_progress_percentage($course));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the course progress returns null for a not tracked for completion user in a course.
|
||||
*/
|
||||
public function test_course_progress_not_tracked_user() {
|
||||
global $DB;
|
||||
|
||||
// Add a course that supports completion.
|
||||
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
|
||||
|
||||
// Enrol a user in the course.
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
|
||||
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
|
||||
|
||||
// Now, mark the course as completed.
|
||||
$ccompletion = new completion_completion(array('course' => $course->id, 'userid' => $user->id));
|
||||
$ccompletion->mark_complete();
|
||||
|
||||
// The course completion should return 100.
|
||||
$this->assertEquals('100', \core_completion\progress::get_course_progress_percentage($course, $user->id));
|
||||
|
||||
// Now make the user's role to be not tracked for completion.
|
||||
unassign_capability('moodle/course:isincompletionreports', $studentrole->id);
|
||||
|
||||
// Check that the result is null now.
|
||||
$this->assertNull(\core_completion\progress::get_course_progress_percentage($course, $user->id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -798,6 +798,12 @@ $CFG->admin = 'admin';
|
||||
// Example:
|
||||
// $CFG->behat_usedeprecated = true;
|
||||
//
|
||||
// If you are using a slow machine, it may help to increase the timeouts that Behat uses. The
|
||||
// following example will increase timeouts by a factor of 3 (using 30 seconds instead of 10
|
||||
// seconds, for instance).
|
||||
// Example:
|
||||
// $CFG->behat_increasetimeout = 3;
|
||||
//
|
||||
// Including feature files from directories outside the dirroot is possible if required. The setting
|
||||
// requires that the running user has executable permissions on all parent directories in the paths.
|
||||
// Example:
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -352,30 +352,21 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
|
||||
* @param {String} image new image name ("i/show", "i/hide", etc.)
|
||||
* @param {String} stringname new string for the action menu item
|
||||
* @param {String} stringcomponent
|
||||
* @param {String} titlestr string for "title" attribute (if different from stringname)
|
||||
* @param {String} titlecomponent
|
||||
* @param {String} titlestr not used
|
||||
* @param {String} titlecomponent not used
|
||||
* @param {String} newaction new value for data-action attribute of the link
|
||||
* @return {Promise} promise which is resolved when the replacement has completed
|
||||
*/
|
||||
var replaceActionItem = function(actionitem, image, stringname,
|
||||
stringcomponent, titlestr, titlecomponent, newaction) {
|
||||
|
||||
|
||||
var stringRequests = [{key: stringname, component: stringcomponent}];
|
||||
if (titlestr) {
|
||||
stringRequests.push({key: titlestr, component: titlecomponent});
|
||||
}
|
||||
// Do not provide an icon with duplicate, different text to the menu item.
|
||||
|
||||
return str.get_strings(stringRequests).then(function(strings) {
|
||||
actionitem.find('span.menu-action-text').html(strings[0]);
|
||||
actionitem.attr('title', strings[0]);
|
||||
|
||||
var title = '';
|
||||
if (titlestr) {
|
||||
title = strings[1];
|
||||
actionitem.attr('title', title);
|
||||
}
|
||||
return templates.renderPix(image, 'core', title);
|
||||
return templates.renderPix(image, 'core');
|
||||
}).then(function(pixhtml) {
|
||||
actionitem.find('.icon').replaceWith(pixhtml);
|
||||
actionitem.attr('data-action', newaction);
|
||||
|
||||
@@ -766,7 +766,7 @@ class helper {
|
||||
*/
|
||||
public static function get_management_viewmodes() {
|
||||
return array(
|
||||
'combined' => new \lang_string('categoriesandcoures'),
|
||||
'combined' => new \lang_string('categoriesandcourses'),
|
||||
'categories' => new \lang_string('categories'),
|
||||
'courses' => new \lang_string('courses')
|
||||
);
|
||||
|
||||
@@ -3447,6 +3447,17 @@ class core_course_external extends external_api {
|
||||
$id = $params['id'];
|
||||
$sectionreturn = $params['sectionreturn'];
|
||||
|
||||
// Set of permissions an editing user may have.
|
||||
$contextarray = [
|
||||
'moodle/course:update',
|
||||
'moodle/course:manageactivities',
|
||||
'moodle/course:activityvisibility',
|
||||
'moodle/course:sectionvisibility',
|
||||
'moodle/course:movesections',
|
||||
'moodle/course:setcurrentsection',
|
||||
];
|
||||
$PAGE->set_other_editing_capability($contextarray);
|
||||
|
||||
list($course, $cm) = get_course_and_cm_from_cmid($id);
|
||||
$modcontext = context_module::instance($cm->id);
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
@@ -3554,6 +3565,17 @@ class core_course_external extends external_api {
|
||||
$id = $params['id'];
|
||||
$sectionreturn = $params['sectionreturn'];
|
||||
|
||||
// Set of permissions an editing user may have.
|
||||
$contextarray = [
|
||||
'moodle/course:update',
|
||||
'moodle/course:manageactivities',
|
||||
'moodle/course:activityvisibility',
|
||||
'moodle/course:sectionvisibility',
|
||||
'moodle/course:movesections',
|
||||
'moodle/course:setcurrentsection',
|
||||
];
|
||||
$PAGE->set_other_editing_capability($contextarray);
|
||||
|
||||
// Validate access to the course (note, this is html for the course view page, we don't validate access to the module).
|
||||
list($course, $cm) = get_course_and_cm_from_cmid($id);
|
||||
self::validate_context(context_course::instance($course->id));
|
||||
|
||||
+14
-15
@@ -117,10 +117,9 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
$name = empty($value['name']) ? '' : $value['name'];
|
||||
$attr = empty($value['attr']) ? array() : $value['attr'];
|
||||
$class = empty($value['pixattr']['class']) ? '' : $value['pixattr']['class'];
|
||||
$alt = empty($value['pixattr']['alt']) ? '' : $value['pixattr']['alt'];
|
||||
$al = new action_menu_link_secondary(
|
||||
new moodle_url($url),
|
||||
new pix_icon($icon, $alt, null, array('class' => "smallicon " . $class)),
|
||||
new pix_icon($icon, '', null, array('class' => "smallicon " . $class)),
|
||||
$name,
|
||||
$attr
|
||||
);
|
||||
@@ -328,8 +327,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
'url' => new moodle_url('/course/editsection.php', array('id' => $section->id, 'sr' => $sectionreturn)),
|
||||
'icon' => 'i/settings',
|
||||
'name' => $streditsection,
|
||||
'pixattr' => array('class' => '', 'alt' => $streditsection),
|
||||
'attr' => array('class' => 'icon edit', 'title' => $streditsection));
|
||||
'pixattr' => array('class' => ''),
|
||||
'attr' => array('class' => 'icon edit'));
|
||||
}
|
||||
|
||||
if ($section->section) {
|
||||
@@ -343,8 +342,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
'url' => $url,
|
||||
'icon' => 'i/hide',
|
||||
'name' => $strhidefromothers,
|
||||
'pixattr' => array('class' => '', 'alt' => $strhidefromothers),
|
||||
'attr' => array('class' => 'icon editing_showhide', 'title' => $strhidefromothers,
|
||||
'pixattr' => array('class' => ''),
|
||||
'attr' => array('class' => 'icon editing_showhide',
|
||||
'data-sectionreturn' => $sectionreturn, 'data-action' => 'hide'));
|
||||
} else {
|
||||
$strshowfromothers = get_string('showfromothers', 'format_'.$course->format);
|
||||
@@ -353,8 +352,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
'url' => $url,
|
||||
'icon' => 'i/show',
|
||||
'name' => $strshowfromothers,
|
||||
'pixattr' => array('class' => '', 'alt' => $strshowfromothers),
|
||||
'attr' => array('class' => 'icon editing_showhide', 'title' => $strshowfromothers,
|
||||
'pixattr' => array('class' => ''),
|
||||
'attr' => array('class' => 'icon editing_showhide',
|
||||
'data-sectionreturn' => $sectionreturn, 'data-action' => 'show'));
|
||||
}
|
||||
}
|
||||
@@ -370,8 +369,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
'url' => $url,
|
||||
'icon' => 'i/up',
|
||||
'name' => $strmoveup,
|
||||
'pixattr' => array('class' => '', 'alt' => $strmoveup),
|
||||
'attr' => array('class' => 'icon moveup', 'title' => $strmoveup));
|
||||
'pixattr' => array('class' => ''),
|
||||
'attr' => array('class' => 'icon moveup'));
|
||||
}
|
||||
|
||||
$url = clone($baseurl);
|
||||
@@ -383,8 +382,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
'url' => $url,
|
||||
'icon' => 'i/down',
|
||||
'name' => $strmovedown,
|
||||
'pixattr' => array('class' => '', 'alt' => $strmovedown),
|
||||
'attr' => array('class' => 'icon movedown', 'title' => $strmovedown));
|
||||
'pixattr' => array('class' => ''),
|
||||
'attr' => array('class' => 'icon movedown'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,8 +404,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
'url' => $url,
|
||||
'icon' => 'i/delete',
|
||||
'name' => $strdelete,
|
||||
'pixattr' => array('class' => '', 'alt' => $strdelete),
|
||||
'attr' => array('class' => 'icon editing_delete', 'title' => $strdelete));
|
||||
'pixattr' => array('class' => ''),
|
||||
'attr' => array('class' => 'icon editing_delete'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1009,7 +1008,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
|
||||
if ($sectionreturn !== null) {
|
||||
$url->param('sectionreturn', $sectionreturn);
|
||||
}
|
||||
$icon = $this->output->pix_icon('t/add', $straddsections);
|
||||
$icon = $this->output->pix_icon('t/add', '');
|
||||
$newsections = $maxsections - $lastsection;
|
||||
echo html_writer::link($url, $icon . $straddsections,
|
||||
array('class' => 'add-sections', 'data-add-sections' => $straddsections, 'new-sections' => $newsections));
|
||||
|
||||
@@ -123,21 +123,19 @@ class format_topics_renderer extends format_section_renderer_base {
|
||||
if ($section->section && has_capability('moodle/course:setcurrentsection', $coursecontext)) {
|
||||
if ($course->marker == $section->section) { // Show the "light globe" on/off.
|
||||
$url->param('marker', 0);
|
||||
$markedthistopic = get_string('markedthistopic');
|
||||
$highlightoff = get_string('highlightoff');
|
||||
$controls['highlight'] = array('url' => $url, "icon" => 'i/marked',
|
||||
'name' => $highlightoff,
|
||||
'pixattr' => array('class' => '', 'alt' => $markedthistopic),
|
||||
'attr' => array('class' => 'editing_highlight', 'title' => $markedthistopic,
|
||||
'pixattr' => array('class' => ''),
|
||||
'attr' => array('class' => 'editing_highlight',
|
||||
'data-action' => 'removemarker'));
|
||||
} else {
|
||||
$url->param('marker', $section->section);
|
||||
$markthistopic = get_string('markthistopic');
|
||||
$highlight = get_string('highlight');
|
||||
$controls['highlight'] = array('url' => $url, "icon" => 'i/marker',
|
||||
'name' => $highlight,
|
||||
'pixattr' => array('class' => '', 'alt' => $markthistopic),
|
||||
'attr' => array('class' => 'editing_highlight', 'title' => $markthistopic,
|
||||
'pixattr' => array('class' => ''),
|
||||
'attr' => array('class' => 'editing_highlight',
|
||||
'data-action' => 'setmarker'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ class format_topics_testcase extends advanced_testcase {
|
||||
$tmpl = component_callback('format_weeks', 'inplace_editable', array('sectionname', $section->id, 'New name'));
|
||||
$this->fail('Exception expected');
|
||||
} catch (moodle_exception $e) {
|
||||
$this->assertEquals(1, preg_match('/^Can not find data record in database/', $e->getMessage()));
|
||||
$this->assertEquals(1, preg_match('/^Can\'t find data record in database/', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ class format_weeks_testcase extends advanced_testcase {
|
||||
$tmpl = component_callback('format_topics', 'inplace_editable', array('sectionname', $section->id, 'New name'));
|
||||
$this->fail('Exception expected');
|
||||
} catch (moodle_exception $e) {
|
||||
$this->assertEquals(1, preg_match('/^Can not find data record in database/', $e->getMessage()));
|
||||
$this->assertEquals(1, preg_match('/^Can\'t find data record in database/', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -1891,7 +1891,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
if ($hasmanageactivities) {
|
||||
$actions['update'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('update' => $mod->id)),
|
||||
new pix_icon('t/edit', $str->editsettings, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
new pix_icon('t/edit', '', 'moodle', array('class' => 'iconsmall')),
|
||||
$str->editsettings,
|
||||
array('class' => 'editing_update', 'data-action' => 'update')
|
||||
);
|
||||
@@ -1917,7 +1917,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
}
|
||||
$actions['moveright'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
|
||||
new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
new pix_icon($rightarrow, '', 'moodle', array('class' => 'iconsmall')),
|
||||
$str->moveright,
|
||||
array('class' => 'editing_moveright ' . $enabledclass, 'data-action' => 'moveright',
|
||||
'data-keepopen' => true, 'data-sectionreturn' => $sr)
|
||||
@@ -1930,7 +1930,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
}
|
||||
$actions['moveleft'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
|
||||
new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
new pix_icon($leftarrow, '', 'moodle', array('class' => 'iconsmall')),
|
||||
$str->moveleft,
|
||||
array('class' => 'editing_moveleft ' . $enabledclass, 'data-action' => 'moveleft',
|
||||
'data-keepopen' => true, 'data-sectionreturn' => $sr)
|
||||
@@ -1953,7 +1953,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
if ($displayedoncoursepage) {
|
||||
$actions['hide'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('hide' => $mod->id)),
|
||||
new pix_icon('t/hide', $str->modhide, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
new pix_icon('t/hide', '', 'moodle', array('class' => 'iconsmall')),
|
||||
$str->modhide,
|
||||
array('class' => 'editing_hide', 'data-action' => 'hide')
|
||||
);
|
||||
@@ -1961,7 +1961,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
// Offer to "show" only if the section is visible.
|
||||
$actions['show'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('show' => $mod->id)),
|
||||
new pix_icon('t/show', $str->modshow, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
new pix_icon('t/show', '', 'moodle', array('class' => 'iconsmall')),
|
||||
$str->modshow,
|
||||
array('class' => 'editing_show', 'data-action' => 'show')
|
||||
);
|
||||
@@ -1971,7 +1971,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
// When making the "stealth" module unavailable we perform the same action as hiding the visible module.
|
||||
$actions['hide'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('hide' => $mod->id)),
|
||||
new pix_icon('t/unblock', $str->makeunavailable, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
new pix_icon('t/unblock', '', 'moodle', array('class' => 'iconsmall')),
|
||||
$str->makeunavailable,
|
||||
array('class' => 'editing_makeunavailable', 'data-action' => 'hide', 'data-sectionreturn' => $sr)
|
||||
);
|
||||
@@ -1982,7 +1982,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
$action = $sectionvisible ? 'stealth' : 'show';
|
||||
$actions[$action] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array($action => $mod->id)),
|
||||
new pix_icon('t/block', $str->makeavailable, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
new pix_icon('t/block', '', 'moodle', array('class' => 'iconsmall')),
|
||||
$str->makeavailable,
|
||||
array('class' => 'editing_makeavailable', 'data-action' => $action, 'data-sectionreturn' => $sr)
|
||||
);
|
||||
@@ -1995,7 +1995,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
course_allowed_module($mod->get_course(), $mod->modname)) {
|
||||
$actions['duplicate'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('duplicate' => $mod->id)),
|
||||
new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
new pix_icon('t/copy', '', 'moodle', array('class' => 'iconsmall')),
|
||||
$str->duplicate,
|
||||
array('class' => 'editing_duplicate', 'data-action' => 'duplicate', 'data-sectionreturn' => $sr)
|
||||
);
|
||||
@@ -2026,7 +2026,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
|
||||
$actions[$actionname] = new action_menu_link_primary(
|
||||
new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $nextgroupmode)),
|
||||
new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall')),
|
||||
new pix_icon($groupimage, '', 'moodle', array('class' => 'iconsmall')),
|
||||
$grouptitle,
|
||||
array('class' => 'editing_'. $actionname, 'data-action' => $nextactionname,
|
||||
'aria-live' => 'assertive', 'data-sectionreturn' => $sr)
|
||||
@@ -2040,7 +2040,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
if (has_capability('moodle/role:assign', $modcontext)){
|
||||
$actions['assign'] = new action_menu_link_secondary(
|
||||
new moodle_url('/admin/roles/assign.php', array('contextid' => $modcontext->id)),
|
||||
new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
new pix_icon('t/assignroles', '', 'moodle', array('class' => 'iconsmall')),
|
||||
$str->assign,
|
||||
array('class' => 'editing_assign', 'data-action' => 'assignroles', 'data-sectionreturn' => $sr)
|
||||
);
|
||||
@@ -2050,7 +2050,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
if ($hasmanageactivities) {
|
||||
$actions['delete'] = new action_menu_link_secondary(
|
||||
new moodle_url($baseurl, array('delete' => $mod->id)),
|
||||
new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')),
|
||||
new pix_icon('t/delete', '', 'moodle', array('class' => 'iconsmall')),
|
||||
$str->delete,
|
||||
array('class' => 'editing_delete', 'data-action' => 'delete', 'data-sectionreturn' => $sr)
|
||||
);
|
||||
|
||||
+1
-1
@@ -648,7 +648,7 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
|
||||
// Display link itself.
|
||||
$activitylink = html_writer::empty_tag('img', array('src' => $mod->get_icon_url(),
|
||||
'class' => 'iconlarge activityicon', 'alt' => ' ', 'role' => 'presentation')) .
|
||||
'class' => 'iconlarge activityicon', 'alt' => '', 'role' => 'presentation', 'aria-hidden' => 'true')) .
|
||||
html_writer::tag('span', $instancename . $altname, array('class' => 'instancename'));
|
||||
if ($mod->uservisible) {
|
||||
$output .= html_writer::link($url, $activitylink, array('class' => $linkclasses, 'onclick' => $onclick));
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ require_capability('moodle/course:request', $context);
|
||||
|
||||
// Set up the form.
|
||||
$data = course_request::prepare();
|
||||
$requestform = new course_request_form($url, compact('editoroptions'));
|
||||
$requestform = new course_request_form($url);
|
||||
$requestform->set_data($data);
|
||||
|
||||
$strtitle = get_string('courserequest');
|
||||
|
||||
@@ -83,7 +83,7 @@ class behat_course extends behat_base {
|
||||
|
||||
// Select Miscellaneous category.
|
||||
$this->i_click_on_category_in_the_management_interface(get_string('miscellaneous'));
|
||||
$this->execute("behat_course::i_should_see_the_courses_management_page", get_string('categoriesandcoures'));
|
||||
$this->execute("behat_course::i_should_see_the_courses_management_page", get_string('categoriesandcourses'));
|
||||
|
||||
// Click create new course.
|
||||
$this->execute('behat_general::i_click_on_in_the',
|
||||
@@ -304,7 +304,7 @@ class behat_course extends behat_base {
|
||||
|
||||
// Click on highlight topic link.
|
||||
$this->execute('behat_general::i_click_on_in_the',
|
||||
array(get_string('markthistopic'), "link", $this->escape($xpath), "xpath_element")
|
||||
array(get_string('highlight'), "link", $this->escape($xpath), "xpath_element")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ class behat_course extends behat_base {
|
||||
|
||||
// Click on un-highlight topic link.
|
||||
$this->execute('behat_general::i_click_on_in_the',
|
||||
array(get_string('markedthistopic'), "link", $this->escape($xpath), "xpath_element")
|
||||
array(get_string('highlightoff'), "link", $this->escape($xpath), "xpath_element")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ class behat_course extends behat_base {
|
||||
$showlink->click();
|
||||
|
||||
if ($this->running_javascript()) {
|
||||
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
|
||||
$this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
|
||||
$this->i_wait_until_section_is_available($sectionnumber);
|
||||
}
|
||||
}
|
||||
@@ -380,7 +380,7 @@ class behat_course extends behat_base {
|
||||
);
|
||||
|
||||
if ($this->running_javascript()) {
|
||||
$this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
|
||||
$this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
|
||||
$this->i_wait_until_section_is_available($sectionnumber);
|
||||
}
|
||||
}
|
||||
@@ -444,7 +444,7 @@ class behat_course extends behat_base {
|
||||
$xpath = $this->section_exists($sectionnumber);
|
||||
|
||||
// The important checking, we can not check the img.
|
||||
$this->execute('behat_general::should_exist_in_the', ['This topic is highlighted as the current topic', 'icon', $xpath, 'xpath_element']);
|
||||
$this->execute('behat_general::should_exist_in_the', ['Remove highlight', 'link', $xpath, 'xpath_element']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -775,11 +775,10 @@ class behat_course extends behat_base {
|
||||
// Ensure the destination is valid.
|
||||
$sectionxpath = $this->section_exists($sectionnumber);
|
||||
|
||||
$activitynode = $this->get_activity_element('Move', 'icon', $activityname);
|
||||
|
||||
// JS enabled.
|
||||
if ($this->running_javascript()) {
|
||||
|
||||
$activitynode = $this->get_activity_element('Move', 'icon', $activityname);
|
||||
$destinationxpath = $sectionxpath . "/descendant::ul[contains(concat(' ', normalize-space(@class), ' '), ' yui3-dd-drop ')]";
|
||||
|
||||
$this->execute("behat_general::i_drag_and_i_drop_it_in",
|
||||
@@ -1165,9 +1164,9 @@ class behat_course extends behat_base {
|
||||
|
||||
// Checking the show button alt text and show icon.
|
||||
$showtext = get_string('showfromothers', $courseformat);
|
||||
$linkxpath = $xpath . "/descendant::a[@title=" . behat_context_helper::escape($showtext) . "]";
|
||||
$linkxpath = $xpath . "//a[*[contains(text(), " . behat_context_helper::escape($showtext) . ")]]";
|
||||
|
||||
$exception = new ElementNotFoundException($this->getSession(), 'Show section link ');
|
||||
$exception = new ElementNotFoundException($this->getSession(), 'Show section link');
|
||||
|
||||
// Returing the link so both Non-JS and JS browsers can interact with it.
|
||||
return $this->find('xpath', $linkxpath, $exception);
|
||||
|
||||
@@ -3426,7 +3426,9 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test reset_course_userdata() with reset_roles_overrides enabled.
|
||||
* Test reset_course_userdata()
|
||||
* - with reset_roles_overrides enabled
|
||||
* - with selective role unenrolments
|
||||
*/
|
||||
public function test_course_roles_reset() {
|
||||
global $DB;
|
||||
@@ -3441,6 +3443,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
$roleid = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
|
||||
$generator->enrol_user($user->id, $course->id, $roleid);
|
||||
|
||||
// Test case with reset_roles_overrides enabled.
|
||||
// Override course so it does NOT allow students 'mod/forum:viewdiscussion'.
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $roleid, $coursecontext->id);
|
||||
@@ -3456,6 +3459,39 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
|
||||
// Check new expected capabilities - override at the course level should be reset.
|
||||
$this->assertTrue(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
|
||||
|
||||
// Test case with selective role unenrolments.
|
||||
$roles = array();
|
||||
$roles['student'] = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
|
||||
$roles['teacher'] = $DB->get_field('role', 'id', array('shortname' => 'teacher'), MUST_EXIST);
|
||||
|
||||
// We enrol a user with student and teacher roles.
|
||||
$generator->enrol_user($user->id, $course->id, $roles['student']);
|
||||
$generator->enrol_user($user->id, $course->id, $roles['teacher']);
|
||||
|
||||
// When we reset only student role, we expect to keep teacher role.
|
||||
$resetdata = new stdClass();
|
||||
$resetdata->id = $course->id;
|
||||
$resetdata->unenrol_users = array($roles['student']);
|
||||
reset_course_userdata($resetdata);
|
||||
|
||||
$usersroles = enrol_get_course_users_roles($course->id);
|
||||
$this->assertArrayHasKey($user->id, $usersroles);
|
||||
$this->assertArrayHasKey($roles['teacher'], $usersroles[$user->id]);
|
||||
$this->assertArrayNotHasKey($roles['student'], $usersroles[$user->id]);
|
||||
$this->assertCount(1, $usersroles[$user->id]);
|
||||
|
||||
// We reenrol user as student.
|
||||
$generator->enrol_user($user->id, $course->id, $roles['student']);
|
||||
|
||||
// When we reset student and teacher roles, we expect no roles left.
|
||||
$resetdata = new stdClass();
|
||||
$resetdata->id = $course->id;
|
||||
$resetdata->unenrol_users = array($roles['student'], $roles['teacher']);
|
||||
reset_course_userdata($resetdata);
|
||||
|
||||
$usersroles = enrol_get_course_users_roles($course->id);
|
||||
$this->assertEmpty($usersroles);
|
||||
}
|
||||
|
||||
public function test_course_check_module_updates_since() {
|
||||
|
||||
@@ -98,6 +98,13 @@ class enrol_database_testcase extends advanced_testcase {
|
||||
case 'mssql':
|
||||
set_config('dbtype', 'mssqlnative', 'enrol_database');
|
||||
set_config('dbsybasequoting', '1', 'enrol_database');
|
||||
|
||||
// The native sqlsrv driver uses a comma as separator between host and port.
|
||||
$dbhost = $CFG->dbhost;
|
||||
if (!empty($dboptions['dbport'])) {
|
||||
$dbhost .= ',' . $dboptions['dbport'];
|
||||
}
|
||||
set_config('dbhost', $dbhost, 'enrol_database');
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -1028,4 +1028,56 @@ class core_enrollib_testcase extends advanced_testcase {
|
||||
|
||||
$this->assertEquals($expectedcourses, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test enrol_get_course_users_roles function.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_enrol_get_course_users_roles() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$user2 = $this->getDataGenerator()->create_user();
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$context = context_course::instance($course->id);
|
||||
|
||||
$roles = array();
|
||||
$roles['student'] = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
|
||||
$roles['teacher'] = $DB->get_field('role', 'id', array('shortname' => 'teacher'), MUST_EXIST);
|
||||
|
||||
$manual = enrol_get_plugin('manual');
|
||||
$this->assertNotEmpty($manual);
|
||||
|
||||
$enrol = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'), '*', MUST_EXIST);
|
||||
|
||||
// Test without enrolments.
|
||||
$this->assertEmpty(enrol_get_course_users_roles($course->id));
|
||||
|
||||
// Test with 1 user, 1 role.
|
||||
$manual->enrol_user($enrol, $user1->id, $roles['student']);
|
||||
$return = enrol_get_course_users_roles($course->id);
|
||||
$this->assertArrayHasKey($user1->id, $return);
|
||||
$this->assertArrayHasKey($roles['student'], $return[$user1->id]);
|
||||
$this->assertArrayNotHasKey($roles['teacher'], $return[$user1->id]);
|
||||
|
||||
// Test with 1 user, 2 role.
|
||||
$manual->enrol_user($enrol, $user1->id, $roles['teacher']);
|
||||
$return = enrol_get_course_users_roles($course->id);
|
||||
$this->assertArrayHasKey($user1->id, $return);
|
||||
$this->assertArrayHasKey($roles['student'], $return[$user1->id]);
|
||||
$this->assertArrayHasKey($roles['teacher'], $return[$user1->id]);
|
||||
|
||||
// Test with another user, 1 role.
|
||||
$manual->enrol_user($enrol, $user2->id, $roles['student']);
|
||||
$return = enrol_get_course_users_roles($course->id);
|
||||
$this->assertArrayHasKey($user1->id, $return);
|
||||
$this->assertArrayHasKey($roles['student'], $return[$user1->id]);
|
||||
$this->assertArrayHasKey($roles['teacher'], $return[$user1->id]);
|
||||
$this->assertArrayHasKey($user2->id, $return);
|
||||
$this->assertArrayHasKey($roles['student'], $return[$user2->id]);
|
||||
$this->assertArrayNotHasKey($roles['teacher'], $return[$user2->id]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ trait gradingform_legacy_polyfill {
|
||||
* This method is used to export any user data this sub-plugin has using the object to get the context and userid.
|
||||
*
|
||||
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
|
||||
* @todo MDL-63137 remove this method.
|
||||
* @todo MDL-63167 remove this method.
|
||||
*
|
||||
* @param context $context Context owner of the data.
|
||||
* @param stdClass $definition Grading definition entry to export.
|
||||
@@ -75,7 +75,7 @@ trait gradingform_legacy_polyfill {
|
||||
* Any call to this method should delete all user data for the context defined.
|
||||
*
|
||||
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
|
||||
* @todo MDL-63137 remove this method.
|
||||
* @todo MDL-63167 remove this method.
|
||||
*
|
||||
* @param context $context Context owner of the data.
|
||||
*/
|
||||
@@ -88,7 +88,7 @@ trait gradingform_legacy_polyfill {
|
||||
* A call to this method should delete user data (where practicle) from the userid and context.
|
||||
*
|
||||
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
|
||||
* @todo MDL-63137 remove this method.
|
||||
* @todo MDL-63167 remove this method.
|
||||
*
|
||||
* @param int $userid The user whose information is to be deleted.
|
||||
* @param context $context Context owner of the data.
|
||||
|
||||
@@ -237,3 +237,7 @@
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
.gradingform_guide_comment_chooser {
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ class test_legacy_polyfill_gradingform_provider implements
|
||||
* This method is used to export any user data this sub-plugin has using the object to get the context and userid.
|
||||
*
|
||||
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
|
||||
* @todo MDL-63137 remove this method.
|
||||
* @todo MDL-63167 remove this method.
|
||||
*
|
||||
* @param context $context Context owner of the data.
|
||||
* @param stdClass $definition Grading definition entry to export.
|
||||
@@ -190,7 +190,7 @@ class test_legacy_polyfill_gradingform_provider implements
|
||||
* Any call to this method should delete all user data for the context defined.
|
||||
*
|
||||
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
|
||||
* @todo MDL-63137 remove this method.
|
||||
* @todo MDL-63167 remove this method.
|
||||
*
|
||||
* @param context $context Context owner of the data.
|
||||
*/
|
||||
@@ -202,7 +202,7 @@ class test_legacy_polyfill_gradingform_provider implements
|
||||
* A call to this method should delete user data (where practicle) from the userid and context.
|
||||
*
|
||||
* @deprecated Since Moodle 3.6 MDL-62535 Please use the methods in the gradingform_provider_v2 interface.
|
||||
* @todo MDL-63137 remove this method.
|
||||
* @todo MDL-63167 remove this method.
|
||||
*
|
||||
* @param int $userid The user whose information is to be deleted.
|
||||
* @param context $context Context owner of the data.
|
||||
|
||||
@@ -221,20 +221,38 @@ class gradeimport_csv_load_data {
|
||||
protected function check_user_exists($value, $userfields) {
|
||||
global $DB;
|
||||
|
||||
$usercheckproblem = false;
|
||||
$user = null;
|
||||
$errorkey = false;
|
||||
// The user may use the incorrect field to match the user. This could result in an exception.
|
||||
try {
|
||||
$user = $DB->get_record('user', array($userfields['field'] => $value));
|
||||
} catch (Exception $e) {
|
||||
$usercheckproblem = true;
|
||||
$field = $userfields['field'];
|
||||
// Fields that can be queried in a case-insensitive manner.
|
||||
$caseinsensitivefields = [
|
||||
'email',
|
||||
'username',
|
||||
];
|
||||
// Build query predicate.
|
||||
if (in_array($field, $caseinsensitivefields)) {
|
||||
// Case-insensitive.
|
||||
$select = $DB->sql_equal($field, ':' . $field, false);
|
||||
} else {
|
||||
// Exact-value.
|
||||
$select = "{$field} = :{$field}";
|
||||
}
|
||||
|
||||
// Make sure the record exists and that there's only one matching record found.
|
||||
$user = $DB->get_record_select('user', $select, array($userfields['field'] => $value), '*', MUST_EXIST);
|
||||
} catch (dml_missing_record_exception $missingex) {
|
||||
$errorkey = 'usermappingerror';
|
||||
} catch (dml_multiple_records_exception $multiex) {
|
||||
$errorkey = 'usermappingerrormultipleusersfound';
|
||||
}
|
||||
// Field may be fine, but no records were returned.
|
||||
if (!$user || $usercheckproblem) {
|
||||
if ($errorkey) {
|
||||
$usermappingerrorobj = new stdClass();
|
||||
$usermappingerrorobj->field = $userfields['label'];
|
||||
$usermappingerrorobj->value = $value;
|
||||
$this->cleanup_import(get_string('usermappingerror', 'grades', $usermappingerrorobj));
|
||||
$this->cleanup_import(get_string($errorkey, 'grades', $usermappingerrorobj));
|
||||
unset($usermappingerrorobj);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -243,57 +243,122 @@ Bobby,Bunce,,"Moodle HQ","Rock on!",student5@example.com,75.00,,75.00,{exportdat
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the user matches a user in the system.
|
||||
* Data provider for \gradeimport_csv_load_data_testcase::test_check_user_exists().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_check_user_exists() {
|
||||
public function check_user_exists_provider() {
|
||||
return [
|
||||
'Fetch by email' => [
|
||||
'email', 's1@example.com', true
|
||||
],
|
||||
'Fetch by email, different case' => [
|
||||
'email', 'S1@EXAMPLE.COM', true
|
||||
],
|
||||
'Fetch data using a non-existent email' => [
|
||||
'email', 's2@example.com', false
|
||||
],
|
||||
'Multiple accounts with the same email' => [
|
||||
'email', 's1@example.com', false, 1
|
||||
],
|
||||
'Fetch data using a valid user ID' => [
|
||||
'id', true, true
|
||||
],
|
||||
'Fetch data using a non-existent user ID' => [
|
||||
'id', false, false
|
||||
],
|
||||
'Fetch data using a valid username' => [
|
||||
'username', 's1', true
|
||||
],
|
||||
'Fetch data using a valid username, different case' => [
|
||||
'username', 'S1', true
|
||||
],
|
||||
'Fetch data using an invalid username' => [
|
||||
'username', 's2', false
|
||||
],
|
||||
'Fetch data using a valid ID Number' => [
|
||||
'idnumber', 's1', true
|
||||
],
|
||||
'Fetch data using an invalid ID Number' => [
|
||||
'idnumber', 's2', false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the user matches a user in the system.
|
||||
*
|
||||
* @dataProvider check_user_exists_provider
|
||||
* @param string $field The field to use for the query.
|
||||
* @param string|boolean $value The field value. When fetching by ID, set true to fetch valid user ID, false otherwise.
|
||||
* @param boolean $successexpected Whether we expect for a user to be found or not.
|
||||
* @param int $allowaccountssameemail Value for $CFG->allowaccountssameemail
|
||||
*/
|
||||
public function test_check_user_exists($field, $value, $successexpected, $allowaccountssameemail = 0) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
|
||||
// Need to add one of the users into the system.
|
||||
$user = new stdClass();
|
||||
$user->firstname = 'Anne';
|
||||
$user->lastname = 'Able';
|
||||
$user->email = 'student7@example.com';
|
||||
$userdetail = $this->getDataGenerator()->create_user($user);
|
||||
$user = $generator->create_user([
|
||||
'firstname' => 'Anne',
|
||||
'lastname' => 'Able',
|
||||
'email' => 's1@example.com',
|
||||
'idnumber' => 's1',
|
||||
'username' => 's1',
|
||||
]);
|
||||
|
||||
if ($allowaccountssameemail) {
|
||||
// Create another user with the same email address.
|
||||
$generator->create_user(['email' => 's1@example.com']);
|
||||
}
|
||||
|
||||
// Since the data provider can't know what user ID to use, do a special handling for ID field tests.
|
||||
if ($field === 'id') {
|
||||
if ($value) {
|
||||
// Test for fetching data using a valid user ID. Use the generated user's ID.
|
||||
$value = $user->id;
|
||||
} else {
|
||||
// Test for fetching data using a non-existent user ID.
|
||||
$value = $user->id + 1;
|
||||
}
|
||||
}
|
||||
|
||||
$userfields = [
|
||||
'field' => $field,
|
||||
'label' => 'Field label: ' . $field
|
||||
];
|
||||
|
||||
$testobject = new phpunit_gradeimport_csv_load_data();
|
||||
|
||||
$testarray = $this->csv_load($this->oktext);
|
||||
// Check whether the user exists. If so, then the user id is returned. Otherwise, it returns null.
|
||||
$userid = $testobject->test_check_user_exists($value, $userfields);
|
||||
|
||||
$userfields = array('field' => 'email', 'label' => 'Email address');
|
||||
// If the user exists then the user id is returned.
|
||||
$userid = $testobject->test_check_user_exists($testarray[0][5] , $userfields);
|
||||
// Check that the user id returned matches with the user that we created.
|
||||
$this->assertEquals($userid, $userdetail->id);
|
||||
if ($successexpected) {
|
||||
// Check that the user id returned matches with the user that we created.
|
||||
$this->assertEquals($user->id, $userid);
|
||||
|
||||
// Check for failure.
|
||||
// Try for an exception.
|
||||
$userfields = array('field' => 'id', 'label' => 'userid');
|
||||
$userid = $testobject->test_check_user_exists($testarray[0][0], $userfields);
|
||||
// Check that the userid is null.
|
||||
$this->assertNull($userid);
|
||||
// Check that there are no errors.
|
||||
$this->assertEmpty($testobject->get_gradebookerrors());
|
||||
|
||||
// Expected error message.
|
||||
$mappingobject = new stdClass();
|
||||
$mappingobject->field = $userfields['label'];
|
||||
$mappingobject->value = $testarray[0][0];
|
||||
$expectederrormessage = get_string('usermappingerror', 'grades', $mappingobject);
|
||||
// Check that expected error message and actual message match.
|
||||
$gradebookerrors = $testobject->get_gradebookerrors();
|
||||
$this->assertEquals($expectederrormessage, $gradebookerrors[0]);
|
||||
} else {
|
||||
// Check that the userid is null.
|
||||
$this->assertNull($userid);
|
||||
|
||||
// The field mapping is correct, but the student does not exist.
|
||||
$userid = $testobject->test_check_user_exists($testarray[1][5], $userfields);
|
||||
// Check that the userid is null.
|
||||
$this->assertNull($userid);
|
||||
// Check that expected error message and actual message match.
|
||||
$gradebookerrors = $testobject->get_gradebookerrors();
|
||||
$mappingobject = (object)[
|
||||
'field' => $userfields['label'],
|
||||
'value' => $value,
|
||||
];
|
||||
if ($allowaccountssameemail) {
|
||||
$expectederrormessage = get_string('usermappingerrormultipleusersfound', 'grades', $mappingobject);
|
||||
} else {
|
||||
$expectederrormessage = get_string('usermappingerror', 'grades', $mappingobject);
|
||||
}
|
||||
|
||||
// Expected error message.
|
||||
$mappingobject = new stdClass();
|
||||
$mappingobject->field = $userfields['label'];
|
||||
$mappingobject->value = $testarray[1][5];
|
||||
$expectederrormessage = get_string('usermappingerror', 'grades', $mappingobject);
|
||||
// Check that expected error message and actual message match.
|
||||
$gradebookerrors = $testobject->get_gradebookerrors();
|
||||
// This is the second error in the array of gradebook errors.
|
||||
$this->assertEquals($expectederrormessage, $gradebookerrors[1]);
|
||||
$this->assertEquals($expectederrormessage, $gradebookerrors[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['direct:view'] = 'Import grades from CSV';
|
||||
$string['direct:view'] = 'Import grades from spreadsheet';
|
||||
$string['pluginname'] = 'Paste from spreadsheet';
|
||||
$string['privacy:metadata'] = 'The import grades from copied spreadsheed plugin does not store any personal data.';
|
||||
$string['userdata'] = 'Help copying data into this form.';
|
||||
|
||||
@@ -61,7 +61,7 @@ Feature: A teacher checks the grade history report in a course
|
||||
# Test filtering by student.
|
||||
And I press "Select users"
|
||||
And I set the field with xpath "//form/input[@class='usp-search-field']" to "Student 1"
|
||||
And I press "Search"
|
||||
And I click on "Search" "button" in the "//div[@class='usp-search']" "xpath_element"
|
||||
And I set the field with xpath "//div[@class='usp-checkbox']/input" to "1"
|
||||
And I press "Finish selecting users"
|
||||
And I press "Submit"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user