Compare commits
286 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b21d86f807 | |||
| 472d293a16 | |||
| 168fa1dda1 | |||
| a8e70bad23 | |||
| 7ed55774f1 | |||
| 784156217a | |||
| 0425db5a80 | |||
| 6e1451fb39 | |||
| 8c274e82f4 | |||
| 4a488b9a5c | |||
| 11e49d6676 | |||
| 6f4e2cb799 | |||
| 7790b6727a | |||
| 20f7e23af4 | |||
| 52b6d73c03 | |||
| 78eacf034a | |||
| 67e3dbab7f | |||
| ed3482f9cc | |||
| 3852238df4 | |||
| 1c51eaa50c | |||
| c329d99d8a | |||
| f48f213e40 | |||
| c2686daae4 | |||
| 28a155e712 | |||
| f90459df6f | |||
| bf0778595f | |||
| 3a257a2808 | |||
| 45f5453b2a | |||
| b74d8568eb | |||
| 9f770c1be6 | |||
| 049bb1d7a7 | |||
| 687047148f | |||
| ab48f37f68 | |||
| 6180264ae8 | |||
| b7905dd4e0 | |||
| 8d470c5802 | |||
| 1d183e38b1 | |||
| 80efaa9828 | |||
| 25c47ba407 | |||
| 33f880fba7 | |||
| 9e9a1db47a | |||
| a711c69d8f | |||
| 2a98eedef0 | |||
| dadccb9237 | |||
| 2ce0766cfc | |||
| d456737d72 | |||
| cef948595b | |||
| 24bf9c39d9 | |||
| adca4fe505 | |||
| a44ce9a024 | |||
| fde001145b | |||
| 85ffac56e8 | |||
| 11b4e810fb | |||
| ab1545b20e | |||
| c3673a9be1 | |||
| 9283ba7380 | |||
| bb41dd7921 | |||
| 8305c3ce9a | |||
| 1592b68f0b | |||
| faf7b144a1 | |||
| 08c359f366 | |||
| b03196fe78 | |||
| ec92de9187 | |||
| c3fcedf205 | |||
| d2dec45ec8 | |||
| 84564c42c8 | |||
| 1084eba9a8 | |||
| 6895074a62 | |||
| d79cd16ae7 | |||
| 9579c751ab | |||
| 1cb1763216 | |||
| 2fb006bfb1 | |||
| fbc937f4b4 | |||
| e30b6117f7 | |||
| d0d16bd8fb | |||
| dc378c70e3 | |||
| 15aa7e0b20 | |||
| 5192605a11 | |||
| c6d2515534 | |||
| 4f3271c536 | |||
| b4025d8655 | |||
| a4ab0db4bd | |||
| 972f50ff90 | |||
| 5aeec826f8 | |||
| 94d2dcb8e1 | |||
| c8273f7d7f | |||
| affb1ae2ff | |||
| fea72a6eb3 | |||
| 64d0d94aeb | |||
| 9b4d87bcc2 | |||
| 18740e4938 | |||
| 261be42ab5 | |||
| 181bef2ed6 | |||
| 94bd3452e4 | |||
| fb9670a9b2 | |||
| 00cbbbc930 | |||
| bb31c5eb3e | |||
| 9d5a987a97 | |||
| a1b7b8b98f | |||
| 1e157fb528 | |||
| b5c1e5379e | |||
| c78a136be4 | |||
| 978fff44ab | |||
| 3b212136de | |||
| 9ffab782f7 | |||
| 30ccbb075c | |||
| 793823bca8 | |||
| dd8b086494 | |||
| 30b2b58c27 | |||
| 8c5a8a25b5 | |||
| f6c9f72a76 | |||
| 7e1223958c | |||
| 43362649e5 | |||
| bddf4eecf6 | |||
| e881a516d0 | |||
| 62c1a7b816 | |||
| 0bca22b30f | |||
| 45d06fe9b6 | |||
| 43930d5e8a | |||
| b226665e80 | |||
| edd91d16fa | |||
| 5c3bdc9259 | |||
| 8dc60e1926 | |||
| b91869f889 | |||
| 25128d16aa | |||
| 62cdf371df | |||
| e6bf955210 | |||
| 4ec7c29b30 | |||
| cf6cdd99d4 | |||
| 1ed9fa8f4a | |||
| f520dbe674 | |||
| b61392fa53 | |||
| c4fc929e3f | |||
| ae320424ef | |||
| 2d14596f5a | |||
| 11e171ffe5 | |||
| 3d5679803d | |||
| bdea78ba61 | |||
| c6fb7763bb | |||
| 1d412ff1df | |||
| 2e93f26f2f | |||
| 81dfa11ba8 | |||
| 6a6caa019f | |||
| c1b3ba3e07 | |||
| 5d412e18e7 | |||
| b73bb7b9c4 | |||
| d4d744d544 | |||
| 4cdcf3a407 | |||
| a0283e7bb3 | |||
| 0ae11f6123 | |||
| c1efa9acf4 | |||
| 5ed2aef513 | |||
| 1466bef278 | |||
| 7132e1a398 | |||
| 4328b93712 | |||
| bdfc738c6f | |||
| 07d4b70470 | |||
| de58d4b0a1 | |||
| 349ebe7f45 | |||
| fe39303913 | |||
| 7abc2da93b | |||
| bf9d8d79d5 | |||
| 155332f4aa | |||
| 24c68767e5 | |||
| 5b38dd9e1e | |||
| 72f730972a | |||
| f5c7fcd181 | |||
| 2d436520dc | |||
| eb5d0d7d59 | |||
| d158589158 | |||
| 268e3b893a | |||
| 54004f286e | |||
| 12baca4b15 | |||
| 08e86bd7b1 | |||
| 36e03f1596 | |||
| b1341895b1 | |||
| 6d3dca55f8 | |||
| d86741395e | |||
| 0f6c21c836 | |||
| 8ae39ac69e | |||
| 957baa0871 | |||
| 678bcecc0d | |||
| 06f6b499eb | |||
| 88aa4e2071 | |||
| 49403f979a | |||
| 28047fa3b1 | |||
| 98292864e8 | |||
| 71f3a7fca9 | |||
| 8a416d4e63 | |||
| 94f4f39ab7 | |||
| ad30b41f91 | |||
| 7803827502 | |||
| a6986b3e36 | |||
| 73b7597a8c | |||
| 9b54ddd465 | |||
| 39dc97aef9 | |||
| 8351210984 | |||
| bfce37757f | |||
| b4d8be2042 | |||
| 7d3b148e94 | |||
| 595e05d2a7 | |||
| 4a776d2f67 | |||
| 975c1d4438 | |||
| 019e7dd26c | |||
| 1b479562cd | |||
| d2b35aea26 | |||
| 3678ad0949 | |||
| 8c4893ffdc | |||
| bab00d9b8c | |||
| 88723a6a1d | |||
| 035c042f1e | |||
| 6ed3f966b7 | |||
| a7f70278c8 | |||
| ae1e1dfbd6 | |||
| 5f417739f3 | |||
| 0d2f34b40d | |||
| e0aa753fe1 | |||
| f001dc447e | |||
| cc80dec71e | |||
| b89ebd255c | |||
| 0c773a6a16 | |||
| b45835b7b8 | |||
| 5aacc83bc4 | |||
| ebb0e91226 | |||
| 3ecf4dff52 | |||
| 3f2b9661d8 | |||
| 6b3d7a34dd | |||
| 2e38a7cc40 | |||
| 95269fdb77 | |||
| 5a1745b254 | |||
| e271e28e78 | |||
| 9cfcb4bf8a | |||
| f25f8f0701 | |||
| 03670af6da | |||
| 13f06f7a8e | |||
| a080d4c0df | |||
| ab7849c105 | |||
| 756e148f31 | |||
| 5a2ca41bba | |||
| c9d1d8141f | |||
| 34616581c5 | |||
| 33ec9e3a37 | |||
| 147b984630 | |||
| 4a2a779e12 | |||
| 6364b15e24 | |||
| b71744bb4d | |||
| 241eacaa7a | |||
| 37f3c318ea | |||
| 4a6de87cae | |||
| a89aaec1af | |||
| 00df049b9a | |||
| a37313a6d1 | |||
| b3c5e4f5d7 | |||
| 5eb38638bb | |||
| aeb85b66f2 | |||
| 7ba288d1d0 | |||
| d7e8f35e91 | |||
| db6f80a784 | |||
| b20a875381 | |||
| f3d6fa182b | |||
| 68a6b6db8d | |||
| 9a66680ae1 | |||
| 8d90570067 | |||
| a30f7f13ea | |||
| d780b1866e | |||
| 273ef0b4e3 | |||
| 8402724494 | |||
| 83b5aea43c | |||
| 51f4f8c612 | |||
| 5d9fb70f70 | |||
| 0368c8e2a8 | |||
| 9fea1cc6e4 | |||
| b09e569189 | |||
| 9afc404947 | |||
| 70d6af63ba | |||
| 4338982098 | |||
| 678ca3ded7 | |||
| 2b36e5be2e | |||
| 82ddfd7ea6 | |||
| da91dd3ea0 | |||
| 121373ec8f | |||
| 8fb613ee9f | |||
| 63b646acb1 | |||
| 8c3e24b46c | |||
| 7a54747c9a | |||
| cd8c136c70 |
+10
-2
@@ -174,7 +174,9 @@ before_script:
|
||||
# Enable test external resources
|
||||
sed -i \
|
||||
-e "/require_once/i \\define('TEST_EXTERNAL_FILES_HTTP_URL', 'http://127.0.0.1:8080');" \
|
||||
-e "/require_once/i \\define('TEST_EXTERNAL_FILES_HTTPS_URL', 'http://127.0.0.1:8080');" \
|
||||
config.php ;
|
||||
|
||||
# Redis cache store tests
|
||||
sed -i \
|
||||
-e "/require_once/i \\define('TEST_CACHESTORE_REDIS_TESTSERVERS', '127.0.0.1');" \
|
||||
@@ -254,8 +256,6 @@ script:
|
||||
if [ "$TASK" = 'PHPUNIT' ];
|
||||
then
|
||||
vendor/bin/phpunit --fail-on-risky --disallow-test-output --verbose;
|
||||
EXTTESTS_HITS=$(docker logs exttests 2>&1 | grep -Fv -e 'AH00558' -e '[pid 1]' | wc -l)
|
||||
echo -e "\nTest local resources number of hits: ${EXTTESTS_HITS}.\n"
|
||||
fi
|
||||
|
||||
- >
|
||||
@@ -292,3 +292,11 @@ script:
|
||||
exit 1 ;
|
||||
fi
|
||||
fi
|
||||
|
||||
after_script:
|
||||
- >
|
||||
if [ "$TASK" = 'PHPUNIT' ];
|
||||
then
|
||||
EXTTESTS_HITS=$(docker logs exttests 2>&1 | grep -Fv -e 'AH00558' -e '[pid 1]' | wc -l)
|
||||
echo -e "\nTest local resources number of hits: ${EXTTESTS_HITS}.\n"
|
||||
fi
|
||||
|
||||
+99
-90
@@ -266,6 +266,7 @@ list($options, $unrecognized) = cli_get_params(
|
||||
);
|
||||
|
||||
$interactive = empty($options['non-interactive']);
|
||||
$skipdatabase = $options['skip-database'];
|
||||
|
||||
// set up language
|
||||
$lang = clean_param($options['lang'], PARAM_SAFEDIR);
|
||||
@@ -637,96 +638,100 @@ do {
|
||||
}
|
||||
} while ($hintdatabase !== '');
|
||||
|
||||
// ask for fullname
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('fullsitename', 'moodle'));
|
||||
// If --skip-database option is provided, we do not need to ask for site fullname, shortname, adminuser, adminpass, adminemail.
|
||||
// These fields will be requested during the database install part.
|
||||
if (!$skipdatabase) {
|
||||
// Ask for fullname.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('fullsitename', 'moodle'));
|
||||
|
||||
if ($options['fullname'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['fullname']);
|
||||
if ($options['fullname'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['fullname']);
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
}
|
||||
|
||||
do {
|
||||
$options['fullname'] = cli_input($prompt, $options['fullname']);
|
||||
} while (empty($options['fullname']));
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
if (empty($options['fullname'])) {
|
||||
$a = (object)['option' => 'fullname', 'value' => $options['fullname']];
|
||||
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
$options['fullname'] = cli_input($prompt, $options['fullname']);
|
||||
} while (empty($options['fullname']));
|
||||
} else {
|
||||
if (empty($options['fullname'])) {
|
||||
$a = (object)array('option'=>'fullname', 'value'=>$options['fullname']);
|
||||
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
|
||||
}
|
||||
}
|
||||
// Ask for shortname.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('shortsitename', 'moodle'));
|
||||
|
||||
// ask for shortname
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('shortsitename', 'moodle'));
|
||||
if ($options['shortname'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['shortname']);
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
}
|
||||
|
||||
if ($options['shortname'] !== '') {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['shortname']);
|
||||
do {
|
||||
$options['shortname'] = cli_input($prompt, $options['shortname']);
|
||||
} while (empty($options['shortname']));
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
if (empty($options['shortname'])) {
|
||||
$a = (object)['option' => 'shortname', 'value' => $options['shortname']];
|
||||
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
$options['shortname'] = cli_input($prompt, $options['shortname']);
|
||||
} while (empty($options['shortname']));
|
||||
} else {
|
||||
if (empty($options['shortname'])) {
|
||||
$a = (object)array('option'=>'shortname', 'value'=>$options['shortname']);
|
||||
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
|
||||
}
|
||||
}
|
||||
|
||||
// ask for admin user name
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('cliadminusername', 'install'));
|
||||
if (!empty($options['adminuser'])) {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['adminuser']);
|
||||
// Ask for admin user name.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('cliadminusername', 'install'));
|
||||
if (!empty($options['adminuser'])) {
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['adminuser']);
|
||||
} else {
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
}
|
||||
do {
|
||||
$options['adminuser'] = cli_input($prompt, $options['adminuser']);
|
||||
} while (empty($options['adminuser']) or $options['adminuser'] === 'guest');
|
||||
} else {
|
||||
if ((empty($options['adminuser']) || $options['adminuser'] === 'guest')) {
|
||||
$a = (object)['option' => 'adminuser', 'value' => $options['adminuser']];
|
||||
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
|
||||
}
|
||||
}
|
||||
|
||||
// Ask for admin user password.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('cliadminpassword', 'install'));
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
do {
|
||||
$options['adminpass'] = cli_input($prompt);
|
||||
} while (empty($options['adminpass']) or $options['adminpass'] === 'admin');
|
||||
} else {
|
||||
if ((empty($options['adminpass']) or $options['adminpass'] === 'admin')) {
|
||||
$a = (object)['option' => 'adminpass', 'value' => $options['adminpass']];
|
||||
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
|
||||
}
|
||||
}
|
||||
do {
|
||||
$options['adminuser'] = cli_input($prompt, $options['adminuser']);
|
||||
} while (empty($options['adminuser']) or $options['adminuser'] === 'guest');
|
||||
} else {
|
||||
if (empty($options['adminuser']) or $options['adminuser'] === 'guest') {
|
||||
$a = (object)array('option'=>'adminuser', 'value'=>$options['adminuser']);
|
||||
|
||||
// Ask for the admin email address.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('cliadminemail', 'install'));
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['adminemail']);
|
||||
$options['adminemail'] = cli_input($prompt, $options['adminemail']);
|
||||
}
|
||||
|
||||
// Validate that the address provided was an e-mail address.
|
||||
if (!empty($options['adminemail']) && !validate_email($options['adminemail'])) {
|
||||
$a = (object)['option' => 'adminemail', 'value' => $options['adminemail']];
|
||||
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
|
||||
}
|
||||
}
|
||||
|
||||
// ask for admin user password
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('cliadminpassword', 'install'));
|
||||
$prompt = get_string('clitypevalue', 'admin');
|
||||
do {
|
||||
$options['adminpass'] = cli_input($prompt);
|
||||
} while (empty($options['adminpass']) or $options['adminpass'] === 'admin');
|
||||
} else {
|
||||
if (empty($options['adminpass']) or $options['adminpass'] === 'admin') {
|
||||
$a = (object)array('option'=>'adminpass', 'value'=>$options['adminpass']);
|
||||
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
|
||||
}
|
||||
}
|
||||
|
||||
// Ask for the admin email address.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('cliadminemail', 'install'));
|
||||
$prompt = get_string('clitypevaluedefault', 'admin', $options['adminemail']);
|
||||
$options['adminemail'] = cli_input($prompt, $options['adminemail']);
|
||||
}
|
||||
|
||||
// Validate that the address provided was an e-mail address.
|
||||
if (!empty($options['adminemail']) && !validate_email($options['adminemail'])) {
|
||||
$a = (object) array('option' => 'adminemail', 'value' => $options['adminemail']);
|
||||
cli_error(get_string('cliincorrectvalueerror', 'admin', $a));
|
||||
}
|
||||
|
||||
// Ask for the upgrade key.
|
||||
if ($interactive) {
|
||||
cli_separator();
|
||||
@@ -745,22 +750,26 @@ if ($options['upgradekey'] !== '') {
|
||||
$CFG->upgradekey = $options['upgradekey'];
|
||||
}
|
||||
|
||||
if ($interactive) {
|
||||
if (!$options['agree-license']) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('copyrightnotice'));
|
||||
echo "Moodle - Modular Object-Oriented Dynamic Learning Environment\n";
|
||||
echo get_string('gpl3')."\n\n";
|
||||
echo get_string('doyouagree')."\n";
|
||||
$prompt = get_string('cliyesnoprompt', 'admin');
|
||||
$input = cli_input($prompt, '', array(get_string('clianswerno', 'admin'), get_string('cliansweryes', 'admin')));
|
||||
if ($input == get_string('clianswerno', 'admin')) {
|
||||
exit(1);
|
||||
// The user does not also need to pass agree-license when --skip-database is provided as the user will need to accept
|
||||
// the license again in the database install part.
|
||||
if (!$skipdatabase) {
|
||||
if ($interactive) {
|
||||
if (!$options['agree-license']) {
|
||||
cli_separator();
|
||||
cli_heading(get_string('copyrightnotice'));
|
||||
echo "Moodle - Modular Object-Oriented Dynamic Learning Environment\n";
|
||||
echo get_string('gpl3')."\n\n";
|
||||
echo get_string('doyouagree')."\n";
|
||||
$prompt = get_string('cliyesnoprompt', 'admin');
|
||||
$input = cli_input($prompt, '', array(get_string('clianswerno', 'admin'), get_string('cliansweryes', 'admin')));
|
||||
if ($input == get_string('clianswerno', 'admin')) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!$options['agree-license'] && !$skipdatabase) {
|
||||
cli_error(get_string('climustagreelicense', 'install'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!$options['agree-license']) {
|
||||
cli_error(get_string('climustagreelicense', 'install'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,7 +818,7 @@ if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
|
||||
cli_error(get_string('pluginschecktodo', 'admin'));
|
||||
}
|
||||
|
||||
if (!$options['skip-database']) {
|
||||
if (!$skipdatabase) {
|
||||
install_cli_database($options, $interactive);
|
||||
// This needs to happen at the end to ensure it occurs after all caches
|
||||
// have been purged for the last time.
|
||||
|
||||
@@ -3163,4 +3163,185 @@
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
<MOODLE version="3.10" requires="3.5">
|
||||
<UNICODE level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unicoderequired" />
|
||||
</FEEDBACK>
|
||||
</UNICODE>
|
||||
<DATABASE level="required">
|
||||
<VENDOR name="mariadb" version="10.2.29" />
|
||||
<VENDOR name="mysql" version="5.7" />
|
||||
<VENDOR name="postgres" version="9.6" />
|
||||
<VENDOR name="mssql" version="11.0" />
|
||||
<VENDOR name="oracle" version="11.2" />
|
||||
</DATABASE>
|
||||
<PHP version="7.2.0" level="required">
|
||||
</PHP>
|
||||
<PCREUNICODE level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="pcreunicodewarning" />
|
||||
</FEEDBACK>
|
||||
</PCREUNICODE>
|
||||
<PHP_EXTENSIONS>
|
||||
<PHP_EXTENSION name="iconv" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="iconvrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="mbstring" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="mbstringrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="curl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="curlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="openssl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="opensslrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="tokenizer" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="tokenizerrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlrpc" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="xmlrpcrecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="soap" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="soaprecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="ctype" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ctyperequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zip" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="ziprequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="zlib" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="gd" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="gdrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="simplexml" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="simplexmlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="spl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="splrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="pcre" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="dom" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xml" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="xmlreader" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="intl" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="intlrequired" />
|
||||
</FEEDBACK>
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="json" level="required">
|
||||
</PHP_EXTENSION>
|
||||
<PHP_EXTENSION name="hash" level="required"/>
|
||||
<PHP_EXTENSION name="fileinfo" level="required"/>
|
||||
</PHP_EXTENSIONS>
|
||||
<PHP_SETTINGS>
|
||||
<PHP_SETTING name="memory_limit" value="96M" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="settingmemorylimit" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="file_uploads" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="settingfileuploads" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
<PHP_SETTING name="opcache.enable" value="1" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="opcacherecommended" />
|
||||
</FEEDBACK>
|
||||
</PHP_SETTING>
|
||||
</PHP_SETTINGS>
|
||||
<CUSTOM_CHECKS>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbstorageengine" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="quizattemptsupgradedmessage" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="slashargumentswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unsupporteddbtablerowformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="unoconvwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="libcurlwarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfileformat" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddbfilepertable" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
|
||||
<FEEDBACK>
|
||||
<ON_ERROR message="unsupporteddblargeprefix" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="ishttpswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="incompleteunicodesupport" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
<CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
|
||||
<FEEDBACK>
|
||||
<ON_CHECK message="sixtyfourbitswarning" />
|
||||
</FEEDBACK>
|
||||
</CUSTOM_CHECK>
|
||||
</CUSTOM_CHECKS>
|
||||
</MOODLE>
|
||||
</COMPATIBILITY_MATRIX>
|
||||
|
||||
+4
-2
@@ -509,7 +509,8 @@ if (!$cache and $version > $CFG->version) { // upgrade
|
||||
// Always verify plugin dependencies!
|
||||
$failed = array();
|
||||
if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
|
||||
echo $output->unsatisfied_dependencies_page($version, $failed, $PAGE->url);
|
||||
echo $output->unsatisfied_dependencies_page($version, $failed, new moodle_url($PAGE->url,
|
||||
array('confirmplugincheck' => 0)));
|
||||
die();
|
||||
}
|
||||
unset($failed);
|
||||
@@ -701,7 +702,8 @@ if (!$cache and moodle_needs_upgrading()) {
|
||||
$failed = array();
|
||||
if (!$pluginman->all_plugins_ok($version, $failed)) {
|
||||
$output = $PAGE->get_renderer('core', 'admin');
|
||||
echo $output->unsatisfied_dependencies_page($version, $failed, $PAGE->url);
|
||||
echo $output->unsatisfied_dependencies_page($version, $failed, new moodle_url($PAGE->url,
|
||||
array('confirmplugincheck' => 0)));
|
||||
die();
|
||||
}
|
||||
unset($failed);
|
||||
|
||||
+2
-2
@@ -1098,7 +1098,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
if (!empty($installabortable[$plugin->component])) {
|
||||
$status .= $this->output->single_button(
|
||||
new moodle_url($this->page->url, array('abortinstall' => $plugin->component)),
|
||||
new moodle_url($this->page->url, array('abortinstall' => $plugin->component, 'confirmplugincheck' => 0)),
|
||||
get_string('cancelinstallone', 'core_plugin'),
|
||||
'post',
|
||||
array('class' => 'actionbutton cancelinstallone d-block mt-1')
|
||||
@@ -1194,7 +1194,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
|
||||
if ($installabortable) {
|
||||
$out .= $this->output->single_button(
|
||||
new moodle_url($this->page->url, array('abortinstallx' => 1)),
|
||||
new moodle_url($this->page->url, array('abortinstallx' => 1, 'confirmplugincheck' => 0)),
|
||||
get_string('cancelinstallall', 'core_plugin', count($installabortable)),
|
||||
'post',
|
||||
array('class' => 'singlebutton cancelinstallall mr-1')
|
||||
|
||||
@@ -99,7 +99,7 @@ $options = array(
|
||||
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP => 'REMOTE_ADDR');
|
||||
$temp->add(new admin_setting_configselect('getremoteaddrconf', new lang_string('getremoteaddrconf', 'admin'),
|
||||
new lang_string('configgetremoteaddrconf', 'admin'),
|
||||
GETREMOTEADDR_SKIP_HTTP_X_FORWARDED_FOR|GETREMOTEADDR_SKIP_HTTP_CLIENT_IP, $options));
|
||||
GETREMOTEADDR_SKIP_DEFAULT, $options));
|
||||
$temp->add(new admin_setting_configtext('reverseproxyignore', new lang_string('reverseproxyignore', 'admin'), new lang_string('configreverseproxyignore', 'admin'), ''));
|
||||
|
||||
$temp->add(new admin_setting_heading('webproxy', new lang_string('webproxy', 'admin'), new lang_string('webproxyinfo', 'admin')));
|
||||
|
||||
@@ -26,10 +26,11 @@ namespace tool_dataprivacy;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use lang_string;
|
||||
use core\persistent;
|
||||
|
||||
/**
|
||||
* Class for loading/storing competencies from the DB.
|
||||
* Class for loading/storing data requests from the DB.
|
||||
*
|
||||
* @copyright 2018 Jun Pataleta
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
@@ -62,6 +63,7 @@ class data_request extends persistent {
|
||||
],
|
||||
'comments' => [
|
||||
'type' => PARAM_TEXT,
|
||||
'message' => new lang_string('errorinvalidrequestcomments', 'tool_dataprivacy'),
|
||||
'default' => ''
|
||||
],
|
||||
'commentsformat' => [
|
||||
@@ -75,7 +77,10 @@ class data_request extends persistent {
|
||||
'default' => FORMAT_PLAIN
|
||||
],
|
||||
'userid' => [
|
||||
'default' => 0,
|
||||
'default' => function() {
|
||||
global $USER;
|
||||
return $USER->id;
|
||||
},
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
'requestedby' => [
|
||||
|
||||
@@ -67,8 +67,8 @@ if (!$manage && !\tool_dataprivacy\api::can_contact_dpo()) {
|
||||
redirect($returnurl, get_string('contactdpoviaprivacypolicy', 'tool_dataprivacy'), 0, \core\output\notification::NOTIFY_ERROR);
|
||||
}
|
||||
|
||||
$mform = new tool_dataprivacy_data_request_form($url->out(false), ['manage' => !empty($manage)]);
|
||||
$mform->set_data(['type' => $requesttype]);
|
||||
$mform = new tool_dataprivacy_data_request_form($url->out(false), ['manage' => !empty($manage),
|
||||
'persistent' => new \tool_dataprivacy\data_request(0, (object) ['type' => $requesttype])]);
|
||||
|
||||
// Data request cancelled.
|
||||
if ($mform->is_cancelled()) {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_request;
|
||||
use tool_dataprivacy\local\helper;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
@@ -36,7 +37,10 @@ require_once($CFG->libdir.'/formslib.php');
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
||||
* @package tool_dataprivacy
|
||||
*/
|
||||
class tool_dataprivacy_data_request_form extends moodleform {
|
||||
class tool_dataprivacy_data_request_form extends \core\form\persistent {
|
||||
|
||||
/** @var string Name of the persistent class. */
|
||||
protected static $persistentclass = data_request::class;
|
||||
|
||||
/** @var bool Flag to indicate whether this form is being rendered for managing data requests or for regular requests. */
|
||||
protected $manage = false;
|
||||
@@ -96,14 +100,13 @@ class tool_dataprivacy_data_request_form extends moodleform {
|
||||
api::DATAREQUEST_TYPE_EXPORT => get_string('requesttypeexport', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_TYPE_DELETE => get_string('requesttypedelete', 'tool_dataprivacy')
|
||||
];
|
||||
|
||||
$mform->addElement('select', 'type', get_string('requesttype', 'tool_dataprivacy'), $options);
|
||||
$mform->setType('type', PARAM_INT);
|
||||
$mform->addHelpButton('type', 'requesttype', 'tool_dataprivacy');
|
||||
|
||||
// Request comments text area.
|
||||
$textareaoptions = ['cols' => 60, 'rows' => 10];
|
||||
$mform->addElement('textarea', 'comments', get_string('requestcomments', 'tool_dataprivacy'), $textareaoptions);
|
||||
$mform->setType('type', PARAM_ALPHANUM);
|
||||
$mform->addHelpButton('comments', 'requestcomments', 'tool_dataprivacy');
|
||||
|
||||
// Action buttons.
|
||||
@@ -129,34 +132,49 @@ class tool_dataprivacy_data_request_form extends moodleform {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default data. Unset the default userid if managing data requests
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function get_default_data() {
|
||||
$data = parent::get_default_data();
|
||||
if ($this->manage) {
|
||||
unset($data->userid);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form validation.
|
||||
*
|
||||
* @param array $data
|
||||
* @param stdClass $data
|
||||
* @param array $files
|
||||
* @param array $errors
|
||||
* @return array
|
||||
* @throws coding_exception
|
||||
* @throws dml_exception
|
||||
*/
|
||||
public function validation($data, $files) {
|
||||
public function extra_validation($data, $files, array &$errors) {
|
||||
global $USER;
|
||||
$errors = [];
|
||||
|
||||
$validrequesttypes = [
|
||||
api::DATAREQUEST_TYPE_EXPORT,
|
||||
api::DATAREQUEST_TYPE_DELETE
|
||||
];
|
||||
if (!in_array($data['type'], $validrequesttypes)) {
|
||||
if (!in_array($data->type, $validrequesttypes)) {
|
||||
$errors['type'] = get_string('errorinvalidrequesttype', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
if (api::has_ongoing_request($data['userid'], $data['type'])) {
|
||||
$userid = $data->userid;
|
||||
|
||||
if (api::has_ongoing_request($userid, $data->type)) {
|
||||
$errors['type'] = get_string('errorrequestalreadyexists', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
// Check if current user can create data deletion request.
|
||||
$userid = $data['userid'];
|
||||
if ($data['type'] == api::DATAREQUEST_TYPE_DELETE) {
|
||||
if ($data->type == api::DATAREQUEST_TYPE_DELETE) {
|
||||
if ($userid == $USER->id) {
|
||||
if (!api::can_create_data_deletion_request_for_self()) {
|
||||
$errors['type'] = get_string('errorcannotrequestdeleteforself', 'tool_dataprivacy');
|
||||
|
||||
@@ -130,6 +130,7 @@ $string['effectiveretentionperioduser'] = '{$a} (since the last time the user ac
|
||||
$string['emailsalutation'] = 'Dear {$a},';
|
||||
$string['errorcannotrequestdeleteforself'] = 'You don\'t have permission to create deletion request for yourself.';
|
||||
$string['errorcannotrequestdeleteforother'] = 'You don\'t have permission to create deletion request for this user.';
|
||||
$string['errorinvalidrequestcomments'] = 'The comments field may contain plain text only.';
|
||||
$string['errorinvalidrequestcreationmethod'] = 'Invalid request creation method!';
|
||||
$string['errorinvalidrequeststatus'] = 'Invalid request status!';
|
||||
$string['errorinvalidrequesttype'] = 'Invalid request type!';
|
||||
|
||||
@@ -95,7 +95,7 @@ function tool_dataprivacy_myprofile_navigation(tree $tree, $user, $iscurrentuser
|
||||
$showsummary = true;
|
||||
}
|
||||
|
||||
if ($showsummary) {
|
||||
if ($showsummary && $iscurrentuser) {
|
||||
$summaryurl = new moodle_url('/admin/tool/dataprivacy/summary.php');
|
||||
$summarynode = new core_user\output\myprofile\node('privacyandpolicies', 'retentionsummary',
|
||||
get_string('dataretentionsummary', 'tool_dataprivacy'), null, $summaryurl);
|
||||
|
||||
@@ -12,8 +12,8 @@ Feature: See the competencies for an activity on the course competencies page.
|
||||
| Test-Comp1 | ID-FW1 |
|
||||
| Test-Comp2 | ID-FW1 |
|
||||
Given the following "courses" exist:
|
||||
| shortname | fullname |
|
||||
| C1 | Course 1 |
|
||||
| shortname | fullname | enablecompletion |
|
||||
| C1 | Course 1 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Student | 1 | student1@example.com |
|
||||
@@ -21,9 +21,9 @@ Feature: See the competencies for an activity on the course competencies page.
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber |
|
||||
| page | PageName1 | PageDesc1 | C1 | PAGE1 |
|
||||
| page | PageName2 | PageDesc2 | C1 | PAGE2 |
|
||||
| activity | name | intro | course | idnumber | completion | completionview |
|
||||
| page | PageName1 | PageDesc1 | C1 | PAGE1 | 1 | 1 |
|
||||
| page | PageName2 | PageDesc2 | C1 | PAGE2 | 1 | 1 |
|
||||
And I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I follow "Course 1"
|
||||
@@ -61,3 +61,15 @@ Feature: See the competencies for an activity on the course competencies page.
|
||||
And I should not see "Test-Comp1"
|
||||
And I should not see "Test-Comp2"
|
||||
And I should see "No competencies have been linked to this activity or resource."
|
||||
|
||||
@javascript
|
||||
Scenario: None course competencies page.
|
||||
When I log in as "student1"
|
||||
And I am on site homepage
|
||||
And I follow "Course 1"
|
||||
And I follow "PageName1"
|
||||
Then I should see "Test page content"
|
||||
And I am on site homepage
|
||||
And I follow "Course 1"
|
||||
And I follow "PageName1"
|
||||
Then I should see "Test page content"
|
||||
|
||||
@@ -48,100 +48,113 @@ if ($hassiteconfig) {
|
||||
|
||||
$ADMIN->add('mobileapp', $temp);
|
||||
|
||||
// Show only mobile settings if the mobile service is enabled.
|
||||
if (!empty($CFG->enablemobilewebservice)) {
|
||||
// General notification about limited features due to app restrictions.
|
||||
$notify = new \core\output\notification(
|
||||
get_string('moodleappsportalfeatureswarning', 'tool_mobile', tool_mobile\api::MOODLE_APPS_PORTAL_URL),
|
||||
\core\output\notification::NOTIFY_WARNING);
|
||||
$featuresnotice = $OUTPUT->render($notify);
|
||||
// General notification about limited features due to app restrictions.
|
||||
$notify = new \core\output\notification(
|
||||
get_string('moodleappsportalfeatureswarning', 'tool_mobile', tool_mobile\api::MOODLE_APPS_PORTAL_URL),
|
||||
\core\output\notification::NOTIFY_WARNING);
|
||||
$featuresnotice = $OUTPUT->render($notify);
|
||||
|
||||
// Type of login.
|
||||
$temp = new admin_settingpage('mobileauthentication', new lang_string('mobileauthentication', 'tool_mobile'));
|
||||
$options = array(
|
||||
tool_mobile\api::LOGIN_VIA_APP => new lang_string('loginintheapp', 'tool_mobile'),
|
||||
tool_mobile\api::LOGIN_VIA_BROWSER => new lang_string('logininthebrowser', 'tool_mobile'),
|
||||
tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER => new lang_string('loginintheembeddedbrowser', 'tool_mobile'),
|
||||
);
|
||||
$temp->add(new admin_setting_configselect('tool_mobile/typeoflogin',
|
||||
new lang_string('typeoflogin', 'tool_mobile'),
|
||||
new lang_string('typeoflogin_desc', 'tool_mobile'), 1, $options));
|
||||
// Type of login.
|
||||
$temp = new admin_settingpage(
|
||||
'mobileauthentication',
|
||||
new lang_string('mobileauthentication', 'tool_mobile'),
|
||||
'moodle/site:config',
|
||||
empty($CFG->enablemobilewebservice)
|
||||
);
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
|
||||
new lang_string('forcedurlscheme_key', 'tool_mobile'),
|
||||
new lang_string('forcedurlscheme', 'tool_mobile'), 'moodlemobile', PARAM_NOTAGS));
|
||||
$options = array(
|
||||
tool_mobile\api::LOGIN_VIA_APP => new lang_string('loginintheapp', 'tool_mobile'),
|
||||
tool_mobile\api::LOGIN_VIA_BROWSER => new lang_string('logininthebrowser', 'tool_mobile'),
|
||||
tool_mobile\api::LOGIN_VIA_EMBEDDED_BROWSER => new lang_string('loginintheembeddedbrowser', 'tool_mobile'),
|
||||
);
|
||||
$temp->add(new admin_setting_configselect('tool_mobile/typeoflogin',
|
||||
new lang_string('typeoflogin', 'tool_mobile'),
|
||||
new lang_string('typeoflogin_desc', 'tool_mobile'), 1, $options));
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/minimumversion',
|
||||
new lang_string('minimumversion_key', 'tool_mobile'),
|
||||
new lang_string('minimumversion', 'tool_mobile'), '', PARAM_NOTAGS));
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
|
||||
new lang_string('forcedurlscheme_key', 'tool_mobile'),
|
||||
new lang_string('forcedurlscheme', 'tool_mobile'), 'moodlemobile', PARAM_NOTAGS));
|
||||
|
||||
$ADMIN->add('mobileapp', $temp);
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/minimumversion',
|
||||
new lang_string('minimumversion_key', 'tool_mobile'),
|
||||
new lang_string('minimumversion', 'tool_mobile'), '', PARAM_NOTAGS));
|
||||
|
||||
// Appearance related settings.
|
||||
$temp = new admin_settingpage('mobileappearance', new lang_string('mobileappearance', 'tool_mobile'));
|
||||
$ADMIN->add('mobileapp', $temp);
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeaturesappearance', '', $featuresnotice));
|
||||
// Appearance related settings.
|
||||
$temp = new admin_settingpage(
|
||||
'mobileappearance',
|
||||
new lang_string('mobileappearance', 'tool_mobile'),
|
||||
'moodle/site:config',
|
||||
empty($CFG->enablemobilewebservice)
|
||||
);
|
||||
|
||||
$temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'tool_mobile'),
|
||||
new lang_string('configmobilecssurl', 'tool_mobile'), '', PARAM_URL));
|
||||
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeaturesappearance', '', $featuresnotice));
|
||||
|
||||
// Reference to Branded Mobile App.
|
||||
if (empty($CFG->disableserviceads_branded)) {
|
||||
$temp->add(new admin_setting_description('moodlebrandedappreference',
|
||||
new lang_string('moodlebrandedapp', 'admin'),
|
||||
new lang_string('moodlebrandedappreference', 'admin')
|
||||
));
|
||||
}
|
||||
$temp->add(new admin_setting_configtext('mobilecssurl', new lang_string('mobilecssurl', 'tool_mobile'),
|
||||
new lang_string('configmobilecssurl', 'tool_mobile'), '', PARAM_URL));
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/smartappbanners',
|
||||
new lang_string('smartappbanners', 'tool_mobile'), ''));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('tool_mobile/enablesmartappbanners',
|
||||
new lang_string('enablesmartappbanners', 'tool_mobile'),
|
||||
new lang_string('enablesmartappbanners_desc', 'tool_mobile'), 0));
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/iosappid', new lang_string('iosappid', 'tool_mobile'),
|
||||
new lang_string('iosappid_desc', 'tool_mobile'), '633359593', PARAM_ALPHANUM));
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/androidappid', new lang_string('androidappid', 'tool_mobile'),
|
||||
new lang_string('androidappid_desc', 'tool_mobile'), 'com.moodle.moodlemobile', PARAM_NOTAGS));
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/setuplink', new lang_string('setuplink', 'tool_mobile'),
|
||||
new lang_string('setuplink_desc', 'tool_mobile'), 'https://download.moodle.org/mobile', PARAM_URL));
|
||||
|
||||
$ADMIN->add('mobileapp', $temp);
|
||||
|
||||
// Features related settings.
|
||||
$temp = new admin_settingpage('mobilefeatures', new lang_string('mobilefeatures', 'tool_mobile'));
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeatures', '', $featuresnotice));
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/logout',
|
||||
new lang_string('logout'), ''));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('tool_mobile/forcelogout',
|
||||
new lang_string('forcelogout', 'tool_mobile'),
|
||||
new lang_string('forcelogout_desc', 'tool_mobile'), 0));
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/features',
|
||||
new lang_string('mobilefeatures', 'tool_mobile'), ''));
|
||||
|
||||
$options = tool_mobile\api::get_features_list();
|
||||
$temp->add(new admin_setting_configmultiselect('tool_mobile/disabledfeatures',
|
||||
new lang_string('disabledfeatures', 'tool_mobile'),
|
||||
new lang_string('disabledfeatures_desc', 'tool_mobile'), array(), $options));
|
||||
|
||||
$temp->add(new admin_setting_configtextarea('tool_mobile/custommenuitems',
|
||||
new lang_string('custommenuitems', 'tool_mobile'),
|
||||
new lang_string('custommenuitems_desc', 'tool_mobile'), '', PARAM_RAW, '50', '10'));
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/language',
|
||||
new lang_string('language'), ''));
|
||||
|
||||
$temp->add(new admin_setting_configtextarea('tool_mobile/customlangstrings',
|
||||
new lang_string('customlangstrings', 'tool_mobile'),
|
||||
new lang_string('customlangstrings_desc', 'tool_mobile'), '', PARAM_RAW, '50', '10'));
|
||||
|
||||
$ADMIN->add('mobileapp', $temp);
|
||||
// Reference to Branded Mobile App.
|
||||
if (empty($CFG->disableserviceads_branded)) {
|
||||
$temp->add(new admin_setting_description('moodlebrandedappreference',
|
||||
new lang_string('moodlebrandedapp', 'admin'),
|
||||
new lang_string('moodlebrandedappreference', 'admin')
|
||||
));
|
||||
}
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/smartappbanners',
|
||||
new lang_string('smartappbanners', 'tool_mobile'), ''));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('tool_mobile/enablesmartappbanners',
|
||||
new lang_string('enablesmartappbanners', 'tool_mobile'),
|
||||
new lang_string('enablesmartappbanners_desc', 'tool_mobile'), 0));
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/iosappid', new lang_string('iosappid', 'tool_mobile'),
|
||||
new lang_string('iosappid_desc', 'tool_mobile'), '633359593', PARAM_ALPHANUM));
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/androidappid', new lang_string('androidappid', 'tool_mobile'),
|
||||
new lang_string('androidappid_desc', 'tool_mobile'), 'com.moodle.moodlemobile', PARAM_NOTAGS));
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/setuplink', new lang_string('setuplink', 'tool_mobile'),
|
||||
new lang_string('setuplink_desc', 'tool_mobile'), 'https://download.moodle.org/mobile', PARAM_URL));
|
||||
|
||||
$ADMIN->add('mobileapp', $temp);
|
||||
|
||||
// Features related settings.
|
||||
$temp = new admin_settingpage(
|
||||
'mobilefeatures',
|
||||
new lang_string('mobilefeatures', 'tool_mobile'),
|
||||
'moodle/site:config',
|
||||
empty($CFG->enablemobilewebservice)
|
||||
);
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/moodleappsportalfeatures', '', $featuresnotice));
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/logout',
|
||||
new lang_string('logout'), ''));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('tool_mobile/forcelogout',
|
||||
new lang_string('forcelogout', 'tool_mobile'),
|
||||
new lang_string('forcelogout_desc', 'tool_mobile'), 0));
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/features',
|
||||
new lang_string('mobilefeatures', 'tool_mobile'), ''));
|
||||
|
||||
$options = tool_mobile\api::get_features_list();
|
||||
$temp->add(new admin_setting_configmultiselect('tool_mobile/disabledfeatures',
|
||||
new lang_string('disabledfeatures', 'tool_mobile'),
|
||||
new lang_string('disabledfeatures_desc', 'tool_mobile'), array(), $options));
|
||||
|
||||
$temp->add(new admin_setting_configtextarea('tool_mobile/custommenuitems',
|
||||
new lang_string('custommenuitems', 'tool_mobile'),
|
||||
new lang_string('custommenuitems_desc', 'tool_mobile'), '', PARAM_RAW, '50', '10'));
|
||||
|
||||
$temp->add(new admin_setting_heading('tool_mobile/language',
|
||||
new lang_string('language'), ''));
|
||||
|
||||
$temp->add(new admin_setting_configtextarea('tool_mobile/customlangstrings',
|
||||
new lang_string('customlangstrings', 'tool_mobile'),
|
||||
new lang_string('customlangstrings_desc', 'tool_mobile'), '', PARAM_RAW, '50', '10'));
|
||||
|
||||
$ADMIN->add('mobileapp', $temp);
|
||||
}
|
||||
|
||||
@@ -138,13 +138,28 @@ class manager {
|
||||
*/
|
||||
const CONFIG_SHIPPED_VERSION = 'shipped_version';
|
||||
|
||||
/**
|
||||
* Helper method to initialize admin page, setting appropriate extra URL parameters
|
||||
*
|
||||
* @param string $action
|
||||
*/
|
||||
protected function setup_admin_externalpage(string $action): void {
|
||||
admin_externalpage_setup('tool_usertours/tours', '', array_filter([
|
||||
'action' => $action,
|
||||
'id' => optional_param('id', 0, PARAM_INT),
|
||||
'tourid' => optional_param('tourid', 0, PARAM_INT),
|
||||
'direction' => optional_param('direction', 0, PARAM_INT),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the entry point for this controller class.
|
||||
*
|
||||
* @param string $action The action to perform.
|
||||
*/
|
||||
public function execute($action) {
|
||||
admin_externalpage_setup('tool_usertours/tours');
|
||||
$this->setup_admin_externalpage($action);
|
||||
|
||||
// Add the main content.
|
||||
switch($action) {
|
||||
case self::ACTION_NEWTOUR:
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 90 B |
@@ -93,6 +93,17 @@ class check_defaults extends XMLDBCheckAction {
|
||||
$physicaldefault = null;
|
||||
}
|
||||
|
||||
// For number fields there are issues with type differences, so let's convert
|
||||
// everything to a float.
|
||||
if ($xmldbfield->getType() === XMLDB_TYPE_NUMBER) {
|
||||
if ($physicaldefault !== null) {
|
||||
$physicaldefault = (float) $physicaldefault;
|
||||
}
|
||||
if ($xmldbdefault !== null) {
|
||||
$xmldbdefault = (float) $xmldbdefault;
|
||||
}
|
||||
}
|
||||
|
||||
// There *is* a default and it's wrong.
|
||||
if ($physicaldefault !== $xmldbdefault) {
|
||||
$xmldbtext = self::display_default($xmldbdefault);
|
||||
|
||||
@@ -624,9 +624,25 @@ class manager {
|
||||
LEFT JOIN {context} ctx ON ap.contextid = ctx.id
|
||||
WHERE ctx.id IS NULL)");
|
||||
|
||||
$contextsql = "SELECT id FROM {context} ctx";
|
||||
$DB->delete_records_select('analytics_predictions', "contextid NOT IN ($contextsql)");
|
||||
$DB->delete_records_select('analytics_indicator_calc', "contextid NOT IN ($contextsql)");
|
||||
// Cleanup analaytics predictions/calcs with MySQL friendly sub-select.
|
||||
$DB->execute("DELETE FROM {analytics_predictions} WHERE id IN (
|
||||
SELECT oldpredictions.id
|
||||
FROM (
|
||||
SELECT p.id
|
||||
FROM {analytics_predictions} p
|
||||
LEFT JOIN {context} ctx ON p.contextid = ctx.id
|
||||
WHERE ctx.id IS NULL
|
||||
) oldpredictions
|
||||
)");
|
||||
|
||||
$DB->execute("DELETE FROM {analytics_indicator_calc} WHERE id IN (
|
||||
SELECT oldcalcs.id FROM (
|
||||
SELECT c.id
|
||||
FROM {analytics_indicator_calc} c
|
||||
LEFT JOIN {context} ctx ON c.contextid = ctx.id
|
||||
WHERE ctx.id IS NULL
|
||||
) oldcalcs
|
||||
)");
|
||||
|
||||
// Clean up stuff that depends on analysable ids that do not exist anymore.
|
||||
|
||||
|
||||
@@ -57,5 +57,13 @@ function xmldb_auth_cas_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.8.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2019111801) {
|
||||
// Normalize the memberattribute_isdn plugin config.
|
||||
set_config('memberattribute_isdn',
|
||||
!empty(get_config('auth_cas', 'memberattribute_isdn')), 'auth_cas');
|
||||
|
||||
upgrade_plugin_savepoint(true, 2019111801, 'auth', 'cas');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -239,9 +239,9 @@ if ($ADMIN->fulltree) {
|
||||
get_string('auth_ldap_memberattribute', 'auth_ldap'), '', PARAM_RAW));
|
||||
|
||||
// Member attribute uses dn.
|
||||
$settings->add(new admin_setting_configtext('auth_cas/memberattribute_isdn',
|
||||
$settings->add(new admin_setting_configselect('auth_cas/memberattribute_isdn',
|
||||
get_string('auth_ldap_memberattribute_isdn_key', 'auth_ldap'),
|
||||
get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), '', PARAM_RAW));
|
||||
get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), 0, $yesno));
|
||||
|
||||
// Object class.
|
||||
$settings->add(new admin_setting_configtext('auth_cas/objectclass',
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2019111800; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->version = 2019111801; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2019111200; // Requires this Moodle version
|
||||
$plugin->component = 'auth_cas'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
|
||||
+6
-3
@@ -603,9 +603,12 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
}
|
||||
}
|
||||
if (!empty($update)) {
|
||||
$authdb->Execute("UPDATE {$this->config->table}
|
||||
SET ".implode(',', $update)."
|
||||
WHERE {$this->config->fielduser}='".$this->ext_addslashes($extusername)."'");
|
||||
$sql = "UPDATE {$this->config->table}
|
||||
SET ".implode(',', $update)."
|
||||
WHERE {$this->config->fielduser} = ?";
|
||||
if (!$authdb->Execute($sql, array($this->ext_addslashes($extusername)))) {
|
||||
print_error('auth_dbupdateerror', 'auth_db');
|
||||
}
|
||||
}
|
||||
$authdb->Close();
|
||||
return true;
|
||||
|
||||
@@ -74,5 +74,6 @@ $string['auth_dbcannotconnect'] = 'Cannot connect to external database.';
|
||||
$string['auth_dbcannotreadtable'] = 'Cannot read external table.';
|
||||
$string['auth_dbtableempty'] = 'External table is empty.';
|
||||
$string['auth_dbcolumnlist'] = 'External table contains the following columns:<br />{$a}';
|
||||
$string['auth_dbupdateerror'] = 'Error updating external database.';
|
||||
$string['pluginname'] = 'External database';
|
||||
$string['privacy:metadata'] = 'The External database authentication plugin does not store any personal data.';
|
||||
|
||||
@@ -49,6 +49,6 @@ class behat_auth_email extends behat_base {
|
||||
$confirmationpath = $confirmationurl->out_as_local_url(false);
|
||||
$url = $confirmationpath . '?' . 'data='. $secret .'/'. $username;
|
||||
|
||||
$this->getSession()->visit($this->locate_path($url));
|
||||
$this->execute('behat_general::i_visit', [$url]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,5 +75,13 @@ function xmldb_auth_ldap_upgrade($oldversion) {
|
||||
// Automatically generated Moodle v3.8.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
|
||||
if ($oldversion < 2019111801) {
|
||||
// Normalize the memberattribute_isdn plugin config.
|
||||
set_config('memberattribute_isdn',
|
||||
!empty(get_config('auth_ldap', 'memberattribute_isdn')), 'auth_ldap');
|
||||
|
||||
upgrade_plugin_savepoint(true, 2019111801, 'auth', 'ldap');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ $string['auth_ldap_ldap_encoding'] = 'Encoding used by the LDAP server, most lik
|
||||
$string['auth_ldap_ldap_encoding_key'] = 'LDAP encoding';
|
||||
$string['auth_ldap_login_settings'] = 'Login settings';
|
||||
$string['auth_ldap_memberattribute'] = 'Optional: Overrides user member attribute, when users belongs to a group. Usually \'member\'';
|
||||
$string['auth_ldap_memberattribute_isdn'] = 'Optional: Overrides handling of member attribute values, either 0 or 1';
|
||||
$string['auth_ldap_memberattribute_isdn'] = 'Overrides handling of member attribute values';
|
||||
$string['auth_ldap_memberattribute_isdn_key'] = 'Member attribute uses dn';
|
||||
$string['auth_ldap_memberattribute_key'] = 'Member attribute';
|
||||
$string['auth_ldap_noconnect'] = 'LDAP-module cannot connect to server: {$a}';
|
||||
|
||||
@@ -142,9 +142,9 @@ if ($ADMIN->fulltree) {
|
||||
get_string('auth_ldap_memberattribute', 'auth_ldap'), '', PARAM_RAW));
|
||||
|
||||
// Member attribute uses dn.
|
||||
$settings->add(new admin_setting_configtext('auth_ldap/memberattribute_isdn',
|
||||
$settings->add(new admin_setting_configselect('auth_ldap/memberattribute_isdn',
|
||||
get_string('auth_ldap_memberattribute_isdn_key', 'auth_ldap'),
|
||||
get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), '', PARAM_RAW));
|
||||
get_string('auth_ldap_memberattribute_isdn', 'auth_ldap'), 0, $yesno));
|
||||
|
||||
// Object class.
|
||||
$settings->add(new admin_setting_configtext('auth_ldap/objectclass',
|
||||
|
||||
@@ -108,16 +108,19 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
|
||||
$o['ou'] = 'users';
|
||||
ldap_add($connection, 'ou='.$o['ou'].','.$topdn, $o);
|
||||
|
||||
$createdusers = array();
|
||||
for ($i=1; $i<=5; $i++) {
|
||||
$this->create_ldap_user($connection, $topdn, $i);
|
||||
$createdusers[] = 'username' . $i;
|
||||
}
|
||||
|
||||
// Set up creators group.
|
||||
$assignedroles = array('username1', 'username2');
|
||||
$o = array();
|
||||
$o['objectClass'] = array('posixGroup');
|
||||
$o['cn'] = 'creators';
|
||||
$o['gidNumber'] = 1;
|
||||
$o['memberUid'] = array('username1', 'username2');
|
||||
$o['memberUid'] = $assignedroles;
|
||||
ldap_add($connection, 'cn='.$o['cn'].','.$topdn, $o);
|
||||
|
||||
$creatorrole = $DB->get_record('role', array('shortname'=>'coursecreator'));
|
||||
@@ -174,15 +177,23 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
|
||||
// Check events, 5 users created with 2 users having roles.
|
||||
$this->assertCount(7, $events);
|
||||
foreach ($events as $index => $event) {
|
||||
$usercreatedindex = array(0, 2, 4, 5, 6);
|
||||
$roleassignedindex = array (1, 3);
|
||||
if (in_array($index, $usercreatedindex)) {
|
||||
$this->assertInstanceOf('\core\event\user_created', $event);
|
||||
}
|
||||
if (in_array($index, $roleassignedindex)) {
|
||||
$this->assertInstanceOf('\core\event\role_assigned', $event);
|
||||
$username = $DB->get_field('user', 'username', array('id' => $event->relateduserid)); // Get username.
|
||||
|
||||
if ($event->eventname === '\core\event\user_created') {
|
||||
$this->assertContains($username, $createdusers);
|
||||
unset($events[$index]); // Remove matching event.
|
||||
|
||||
} else if ($event->eventname === '\core\event\role_assigned') {
|
||||
$this->assertContains($username, $assignedroles);
|
||||
unset($events[$index]); // Remove matching event.
|
||||
|
||||
} else {
|
||||
$this->fail('Unexpected event found: ' . $event->eventname);
|
||||
}
|
||||
}
|
||||
// If all the user_created and role_assigned events have matched
|
||||
// then the $events array should be now empty.
|
||||
$this->assertCount(0, $events);
|
||||
|
||||
$this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
|
||||
$this->assertEquals(2, $DB->count_records('role_assignments'));
|
||||
|
||||
@@ -25,6 +25,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2019111800; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->version = 2019111801; // The current plugin version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2019111200; // Requires this Moodle version
|
||||
$plugin->component = 'auth_ldap'; // Full name of the plugin (used for diagnostics)
|
||||
|
||||
@@ -58,7 +58,7 @@ class behat_auth extends behat_base {
|
||||
}
|
||||
|
||||
// Visit login page.
|
||||
$this->getSession()->visit($this->locate_path($loginurl->out_as_local_url()));
|
||||
$this->execute('behat_general::i_visit', [$loginurl]);
|
||||
|
||||
// Enter username and password.
|
||||
$this->execute('behat_forms::i_set_the_field_to', array('Username', $this->escape($username)));
|
||||
|
||||
@@ -122,7 +122,7 @@ class restore_gradebook_structure_step extends restore_structure_step {
|
||||
}
|
||||
|
||||
// Identify the backup we're dealing with.
|
||||
$backuprelease = floatval($this->get_task()->get_info()->backup_release); // The major version: 2.9, 3.0, ...
|
||||
$backuprelease = $this->get_task()->get_info()->backup_release; // The major version: 2.9, 3.0, 3.10...
|
||||
$backupbuild = 0;
|
||||
preg_match('/(\d{8})/', $this->get_task()->get_info()->moodle_release, $matches);
|
||||
if (!empty($matches[1])) {
|
||||
@@ -132,7 +132,7 @@ class restore_gradebook_structure_step extends restore_structure_step {
|
||||
// On older versions the freeze value has to be converted.
|
||||
// We do this from here as it is happening right before the file is read.
|
||||
// This only targets the backup files that can contain the legacy freeze.
|
||||
if ($backupbuild > 20150618 && ($backuprelease < 3.0 || $backupbuild < 20160527)) {
|
||||
if ($backupbuild > 20150618 && (version_compare($backuprelease, '3.0', '<') || $backupbuild < 20160527)) {
|
||||
$this->rewrite_step_backup_file_for_legacy_freeze($fullpath);
|
||||
}
|
||||
|
||||
@@ -505,8 +505,7 @@ class restore_gradebook_structure_step extends restore_structure_step {
|
||||
$gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->get_courseid());
|
||||
preg_match('/(\d{8})/', $this->get_task()->get_info()->moodle_release, $matches);
|
||||
$backupbuild = (int)$matches[1];
|
||||
// The function floatval will return a float even if there is text mixed with the release number.
|
||||
$backuprelease = floatval($this->get_task()->get_info()->backup_release);
|
||||
$backuprelease = $this->get_task()->get_info()->backup_release; // The major version: 2.9, 3.0, 3.10...
|
||||
|
||||
// Extra credits need adjustments only for backups made between 2.8 release (20141110) and the fix release (20150619).
|
||||
if (!$gradebookcalculationsfreeze && $backupbuild >= 20141110 && $backupbuild < 20150619) {
|
||||
@@ -521,7 +520,7 @@ class restore_gradebook_structure_step extends restore_structure_step {
|
||||
// Courses from before 3.1 (20160518) may have a letter boundary problem and should be checked for this issue.
|
||||
// Backups from before and including 2.9 could have a build number that is greater than 20160518 and should
|
||||
// be checked for this problem.
|
||||
if (!$gradebookcalculationsfreeze && ($backupbuild < 20160518 || $backuprelease <= 2.9)) {
|
||||
if (!$gradebookcalculationsfreeze && ($backupbuild < 20160518 || version_compare($backuprelease, '2.9', '<='))) {
|
||||
require_once($CFG->libdir . '/db/upgradelib.php');
|
||||
upgrade_course_letter_boundary($this->get_courseid());
|
||||
}
|
||||
@@ -4577,11 +4576,11 @@ class restore_create_categories_and_questions extends restore_structure_step {
|
||||
|
||||
// Before 3.5, question categories could be created at top level.
|
||||
// From 3.5 onwards, all question categories should be a child of a special category called the "top" category.
|
||||
$backuprelease = floatval($this->get_task()->get_info()->backup_release);
|
||||
$backuprelease = $this->get_task()->get_info()->backup_release; // The major version: 2.9, 3.0, 3.10...
|
||||
preg_match('/(\d{8})/', $this->get_task()->get_info()->moodle_release, $matches);
|
||||
$backupbuild = (int)$matches[1];
|
||||
$before35 = false;
|
||||
if ($backuprelease < 3.5 || $backupbuild < 20180205) {
|
||||
if (version_compare($backuprelease, '3.5', '<') || $backupbuild < 20180205) {
|
||||
$before35 = true;
|
||||
}
|
||||
if (empty($mapping->info->parent) && $before35) {
|
||||
@@ -4838,11 +4837,11 @@ class restore_move_module_questions_categories extends restore_execution_step {
|
||||
protected function define_execution() {
|
||||
global $DB;
|
||||
|
||||
$backuprelease = floatval($this->task->get_info()->backup_release);
|
||||
$backuprelease = $this->task->get_info()->backup_release; // The major version: 2.9, 3.0, 3.10...
|
||||
preg_match('/(\d{8})/', $this->task->get_info()->moodle_release, $matches);
|
||||
$backupbuild = (int)$matches[1];
|
||||
$after35 = false;
|
||||
if ($backuprelease >= 3.5 && $backupbuild > 20180205) {
|
||||
if (version_compare($backuprelease, '3.5', '>=') && $backupbuild > 20180205) {
|
||||
$after35 = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -581,11 +581,11 @@ abstract class restore_dbops {
|
||||
$rc = restore_controller_dbops::load_controller($restoreid);
|
||||
$restoreinfo = $rc->get_info();
|
||||
$rc->destroy(); // Always need to destroy.
|
||||
$backuprelease = floatval($restoreinfo->backup_release);
|
||||
$backuprelease = $restoreinfo->backup_release; // The major version: 2.9, 3.0, 3.10...
|
||||
preg_match('/(\d{8})/', $restoreinfo->moodle_release, $matches);
|
||||
$backupbuild = (int)$matches[1];
|
||||
$after35 = false;
|
||||
if ($backuprelease >= 3.5 && $backupbuild > 20180205) {
|
||||
if (version_compare($backuprelease, '3.5', '>=') && $backupbuild > 20180205) {
|
||||
$after35 = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -402,7 +402,8 @@ abstract class base_moodleform extends moodleform {
|
||||
|
||||
// Get list of module types on course.
|
||||
$modinfo = get_fast_modinfo($COURSE);
|
||||
$modnames = $modinfo->get_used_module_names(true);
|
||||
$modnames = array_map('strval', $modinfo->get_used_module_names(true));
|
||||
core_collator::asort($modnames);
|
||||
$PAGE->requires->yui_module('moodle-backup-backupselectall', 'M.core_backup.backupselectall',
|
||||
array($modnames));
|
||||
$PAGE->requires->strings_for_js(array('select', 'all', 'none'), 'moodle');
|
||||
|
||||
@@ -56,7 +56,7 @@ class behat_backup extends behat_base {
|
||||
// table elements are used, and we need to catch exceptions contantly.
|
||||
|
||||
// Go to homepage.
|
||||
$this->getSession()->visit($this->locate_path('/?redirect=0'));
|
||||
$this->execute('behat_general::i_visit', ['/?redirect=0']);
|
||||
$this->execute("behat_general::wait_until_the_page_is_ready");
|
||||
|
||||
// Click the course link.
|
||||
@@ -98,7 +98,7 @@ class behat_backup extends behat_base {
|
||||
// table elements are used, and we need to catch exceptions contantly.
|
||||
|
||||
// Go to homepage.
|
||||
$this->getSession()->visit($this->locate_path('/?redirect=0'));
|
||||
$this->execute('behat_general::i_visit', ['/?redirect=0']);
|
||||
|
||||
// Click the course link.
|
||||
$this->execute("behat_general::click_link", $backupcourse);
|
||||
@@ -134,7 +134,7 @@ class behat_backup extends behat_base {
|
||||
// table elements are used, and we need to catch exceptions contantly.
|
||||
|
||||
// Go to homepage.
|
||||
$this->getSession()->visit($this->locate_path('/?redirect=0'));
|
||||
$this->execute('behat_general::i_visit', ['/?redirect=0']);
|
||||
$this->execute("behat_general::wait_until_the_page_is_ready");
|
||||
|
||||
// Click the course link.
|
||||
|
||||
@@ -70,7 +70,7 @@ class badge extends moodleform {
|
||||
$mform->addRule('description', null, 'required');
|
||||
|
||||
$str = $action == 'new' ? get_string('badgeimage', 'badges') : get_string('newimage', 'badges');
|
||||
$imageoptions = array('maxbytes' => 262144, 'accepted_types' => array('web_image'));
|
||||
$imageoptions = array('maxbytes' => 262144, 'accepted_types' => array('optimised_image'));
|
||||
$mform->addElement('filepicker', 'image', $str, null, $imageoptions);
|
||||
|
||||
if ($action == 'new') {
|
||||
|
||||
@@ -9,9 +9,14 @@ Feature: Random glossary entry block linking to global glossary
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
| Course 2 | C2 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | globalglossary | defaultapproval |
|
||||
| glossary | Tips and Tricks | Frontpage glossary description | C2 | glossary0 | 1 | 1 |
|
||||
And the following "activity" exists:
|
||||
| activity | glossary |
|
||||
| name | Tips and Tricks |
|
||||
| intro | Frontpage glossary description |
|
||||
| course | C2 |
|
||||
| idnumber | glossary0 |
|
||||
| globalglossary | 1 |
|
||||
| defaultapproval | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Sam1 | Student1 | student1@example.com |
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("block_recentlyaccessedcourses/main",["jquery","core/custom_interaction_events","core/notification","core/pubsub","core/paged_content_paging_bar","core/templates","core_course/events","core_course/repository"],function(a,b,c,d,e,f,g,h){var i={BLOCK_CONTAINER:"[data-region=\"recentlyaccessedcourses\"]",CARD_CONTAINER:"[data-region=\"card-deck\"]",COURSE_IS_FAVOURITE:"[data-region=\"is-favourite\"]",CONTENT:"[data-region=\"view-content\"]",EMPTY_MESSAGE:"[data-region=\"empty-message\"]",LOADING_PLACEHOLDER:"[data-region=\"loading-placeholder\"]",PAGING_BAR:"[data-region=\"paging-bar\"]",PAGING_BAR_NEXT:"[data-control=\"next\"]",PAGING_BAR_PREVIOUS:"[data-control=\"previous\"]"},j=!1,k=[],l=null,m=null,n=0,o=1,p=function(a){a.find(i.EMPTY_MESSAGE).removeClass("hidden");a.find(i.LOADING_PLACEHOLDER).addClass("hidden");a.find(i.CONTENT).addClass("hidden")},q=function(a){a.find(i.CONTENT).removeClass("hidden");a.find(i.EMPTY_MESSAGE).addClass("hidden");a.find(i.LOADING_PLACEHOLDER).addClass("hidden")},r=function(a){var b=a.find(i.PAGING_BAR);b.css("opacity",1);b.css("visibility","visible");b.attr("aria-hidden","false")},s=function(a){var b=a.find(i.PAGING_BAR);b.css("opacity",0);b.css("visibility","hidden");b.attr("aria-hidden","true")},t=function(a,b){k.forEach(function(a){if(a.attr("data-course-id")==b){a.find(i.COURSE_IS_FAVOURITE).removeClass("hidden")}})},u=function(a,b){k.forEach(function(a){if(a.attr("data-course-id")==b){a.find(i.COURSE_IS_FAVOURITE).addClass("hidden")}})},v=function(b){var d=a(i.BLOCK_CONTAINER).data("displaycoursecategory"),e=b.map(function(a){a.showcoursecategory=d;return f.render("block_recentlyaccessedcourses/course-card",a)});return a.when.apply(null,e).then(function(){var b=[];e.forEach(function(d){d.then(function(c){b.push(a(c))}).catch(c.exception)});return b})},w=function(a){return h.getLastAccessedCourses(a,10).then(function(a){return v(a)})},x=function(a){var b=a.find(i.CONTENT).find(i.CARD_CONTAINER),c=parseFloat(a.css("width")),d=k.length,f=0;if(!m){b.html(k[0]);m=k[0].outerWidth(!0)}o=Math.floor(c/m);if(n+o<d){f=n}else{var g=n+o-d;f=n-g;f=0<=f?f:0}var h=k.slice(f,f+o),j=h.reduce(function(a,b){return a+b.attr("data-course-id")},"");if(k.length>h.length){b.addClass("justify-content-center");b.removeClass("justify-content-start")}else{b.removeClass("justify-content-center");b.addClass("justify-content-start")}if(l!=j){var p=a.find(e.rootSelector);b.html(h);l=j;if(o>=k.length){s(a)}else{r(a);if(0===n){e.disablePreviousControlButtons(p)}else{e.enablePreviousControlButtons(p)}if(n+o>=k.length){e.disableNextControlButtons(p)}else{e.enableNextControlButtons(p)}}}},y=function(c){var e=null,f=!1;d.subscribe(g.favourited,function(a){t(c,a)});d.subscribe(g.unfavorited,function(a){u(c,a)});d.subscribe("nav-drawer-toggle-start",function(){if(!j||!k.length||f){return}f=!0;var a=0,b=function(){setTimeout(function(){x(c);a++;if(5>a&&f){b()}},100)};b(c)});d.subscribe("nav-drawer-toggle-end",function(){f=!1});a(window).on("resize",function(){if(!j||!k.length){return}if(!e){e=setTimeout(function(){e=null;x(c)},66)}});b.define(c,[b.events.activate]);c.on(b.events.activate,i.PAGING_BAR_NEXT,function(b,d){var e=a(b.target).closest(i.PAGING_BAR_NEXT);if(!e.hasClass("disabled")){n=n+o;x(c)}d.originalEvent.preventDefault()});c.on(b.events.activate,i.PAGING_BAR_PREVIOUS,function(b,d){var e=a(b.target).closest(i.PAGING_BAR_PREVIOUS);if(!e.hasClass("disabled")){n=n-o;n=0>n?0:n;x(c)}d.originalEvent.preventDefault()})};return{init:function init(b,d){d=a(d);y(d);w(b).then(function(a){k=a;j=!0;if(k.length){q(d);x(d)}else{p(d)}}).catch(c.exception)}}});
|
||||
define ("block_recentlyaccessedcourses/main",["jquery","core/custom_interaction_events","core/notification","core/pubsub","core/paged_content_paging_bar","core/templates","core_course/events","core_course/repository"],function(a,b,c,d,e,f,g,h){var i={BLOCK_CONTAINER:"[data-region=\"recentlyaccessedcourses\"]",CARD_CONTAINER:"[data-region=\"card-deck\"]",COURSE_IS_FAVOURITE:"[data-region=\"is-favourite\"]",CONTENT:"[data-region=\"view-content\"]",EMPTY_MESSAGE:"[data-region=\"empty-message\"]",LOADING_PLACEHOLDER:"[data-region=\"loading-placeholder\"]",PAGING_BAR:"[data-region=\"paging-bar\"]",PAGING_BAR_NEXT:"[data-control=\"next\"]",PAGING_BAR_PREVIOUS:"[data-control=\"previous\"]"},j=!1,k=[],l=null,m=null,n=0,o=1,p=function(a){a.find(i.EMPTY_MESSAGE).removeClass("hidden");a.find(i.LOADING_PLACEHOLDER).addClass("hidden");a.find(i.CONTENT).addClass("hidden")},q=function(a){a.find(i.CONTENT).removeClass("hidden");a.find(i.EMPTY_MESSAGE).addClass("hidden");a.find(i.LOADING_PLACEHOLDER).addClass("hidden")},r=function(a){var b=a.find(i.PAGING_BAR);b.css("opacity",1);b.css("visibility","visible");b.attr("aria-hidden","false")},s=function(a){var b=a.find(i.PAGING_BAR);b.css("opacity",0);b.css("visibility","hidden");b.attr("aria-hidden","true")},t=function(a,b){k.forEach(function(a){if(a.attr("data-course-id")==b){a.find(i.COURSE_IS_FAVOURITE).removeClass("hidden")}})},u=function(a,b){k.forEach(function(a){if(a.attr("data-course-id")==b){a.find(i.COURSE_IS_FAVOURITE).addClass("hidden")}})},v=function(b){var d=a(i.BLOCK_CONTAINER).data("displaycoursecategory"),e=b.map(function(a){a.showcoursecategory=d;return f.render("block_recentlyaccessedcourses/course-card",a)});return a.when.apply(null,e).then(function(){var b=[];e.forEach(function(d){d.then(function(c){b.push(a(c))}).catch(c.exception)});return b})},w=function(a){return h.getLastAccessedCourses(a,10).then(function(a){return v(a)})},x=function(a){var b=a.find(i.CONTENT).find(i.CARD_CONTAINER),c=parseFloat(a.css("width")),d=k.length,f=0;if(!m){b.html(k[0]);m=k[0].outerWidth(!0)}o=Math.floor(c/m);if(n+o<d){f=n}else{var g=n+o-d;f=n-g;f=0<=f?f:0}if(0===o){o=1}var h=k.slice(f,f+o),j=h.reduce(function(a,b){return a+b.attr("data-course-id")},"");if(k.length>h.length){b.addClass("justify-content-center");b.removeClass("justify-content-start")}else{b.removeClass("justify-content-center");b.addClass("justify-content-start")}if(l!=j){var p=a.find(e.rootSelector);b.html(h);l=j;if(o>=k.length){s(a)}else{r(a);if(0===n){e.disablePreviousControlButtons(p)}else{e.enablePreviousControlButtons(p)}if(n+o>=k.length){e.disableNextControlButtons(p)}else{e.enableNextControlButtons(p)}}}},y=function(c){var e=null,f=!1;d.subscribe(g.favourited,function(a){t(c,a)});d.subscribe(g.unfavorited,function(a){u(c,a)});d.subscribe("nav-drawer-toggle-start",function(){if(!j||!k.length||f){return}f=!0;var a=0,b=function(){setTimeout(function(){x(c);a++;if(5>a&&f){b()}},100)};b(c)});d.subscribe("nav-drawer-toggle-end",function(){f=!1});a(window).on("resize",function(){if(!j||!k.length){return}if(!e){e=setTimeout(function(){e=null;x(c)},66)}});b.define(c,[b.events.activate]);c.on(b.events.activate,i.PAGING_BAR_NEXT,function(b,d){var e=a(b.target).closest(i.PAGING_BAR_NEXT);if(!e.hasClass("disabled")){n=n+o;x(c)}d.originalEvent.preventDefault()});c.on(b.events.activate,i.PAGING_BAR_PREVIOUS,function(b,d){var e=a(b.target).closest(i.PAGING_BAR_PREVIOUS);if(!e.hasClass("disabled")){n=n-o;n=0>n?0:n;x(c)}d.originalEvent.preventDefault()})};return{init:function init(b,d){d=a(d);y(d);w(b).then(function(a){k=a;j=!0;if(k.length){q(d);x(d)}else{p(d)}}).catch(c.exception)}}});
|
||||
//# sourceMappingURL=main.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -208,6 +208,11 @@ define(
|
||||
start = start >= 0 ? start : 0;
|
||||
}
|
||||
|
||||
// At least show one card.
|
||||
if (availableVisibleCards === 0) {
|
||||
availableVisibleCards = 1;
|
||||
}
|
||||
|
||||
var coursesToShow = allCourses.slice(start, start + availableVisibleCards);
|
||||
// Create an id for the list of courses we expect to be displayed.
|
||||
var newVisibleCoursesId = coursesToShow.reduce(function(carry, course) {
|
||||
|
||||
+11
-6
@@ -49,13 +49,18 @@ class recentlyaccesseditems_item_exporter extends \core\external\exporter {
|
||||
* @return array Additional properties with values
|
||||
*/
|
||||
protected function get_other_values(renderer_base $output) {
|
||||
global $OUTPUT;
|
||||
global $CFG;
|
||||
require_once($CFG->libdir.'/modinfolib.php');
|
||||
|
||||
return array(
|
||||
'viewurl' => (new moodle_url('/mod/'.$this->data->modname.'/view.php',
|
||||
array('id' => $this->data->cmid)))->out(false),
|
||||
'courseviewurl' => (new moodle_url('/course/view.php', array('id' => $this->data->courseid)))->out(false),
|
||||
'icon' => $OUTPUT->image_icon('icon', get_string('pluginname', $this->data->modname), $this->data->modname)
|
||||
'viewurl' => (new moodle_url('/mod/'.$this->data->modname.'/view.php',
|
||||
array('id' => $this->data->cmid)))->out(false),
|
||||
'courseviewurl' => (new moodle_url('/course/view.php', array('id' => $this->data->courseid)))->out(false),
|
||||
'icon' => \html_writer::img(
|
||||
get_fast_modinfo($this->data->courseid)->cms[$this->data->cmid]->get_icon_url(),
|
||||
get_string('pluginname', $this->data->modname),
|
||||
['title' => get_string('pluginname', $this->data->modname), 'class' => 'icon']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -111,4 +116,4 @@ class recentlyaccesseditems_item_exporter extends \core\external\exporter {
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
data-midnight="{{midnight}}"
|
||||
data-limit="2"
|
||||
data-offset="0"
|
||||
data-days-limit="30"
|
||||
data-days-limit="{{dayslimit}}"
|
||||
data-days-offset="0"
|
||||
data-no-events-url="{{urls.noevents}}"
|
||||
id="view_courses_{{uniqid}}"
|
||||
|
||||
+25
@@ -86,6 +86,9 @@ class week_day_exporter extends day_exporter {
|
||||
'type' => PARAM_RAW,
|
||||
'default' => '',
|
||||
],
|
||||
'daytitle' => [
|
||||
'type' => PARAM_RAW,
|
||||
]
|
||||
]);
|
||||
|
||||
return $return;
|
||||
@@ -104,6 +107,8 @@ class week_day_exporter extends day_exporter {
|
||||
$return['popovertitle'] = $popovertitle;
|
||||
}
|
||||
|
||||
$return['daytitle'] = $this->get_day_title();
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -141,4 +146,24 @@ class week_day_exporter extends day_exporter {
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title for this day.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_day_title(): string {
|
||||
$userdate = userdate($this->data[0], get_string('strftimedayshort'));
|
||||
|
||||
$numevents = count($this->related['events']);
|
||||
if ($numevents == 1) {
|
||||
$title = get_string('dayeventsone', 'calendar', $userdate);
|
||||
} else if ($numevents) {
|
||||
$title = get_string('dayeventsmany', 'calendar', ['num' => $numevents, 'day' => $userdate]);
|
||||
} else {
|
||||
$title = get_string('dayeventsnone', 'calendar', $userdate);
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1274,6 +1274,12 @@ class calendar_information {
|
||||
* @param string|null $view preference view options (eg: day, month, upcoming)
|
||||
*/
|
||||
public function add_sidecalendar_blocks(core_calendar_renderer $renderer, $showfilters=false, $view=null) {
|
||||
global $PAGE;
|
||||
|
||||
if (!has_capability('moodle/block:view', $PAGE->context) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($showfilters) {
|
||||
$filters = new block_contents();
|
||||
$filters->content = $renderer->event_filter();
|
||||
|
||||
@@ -46,8 +46,9 @@
|
||||
<thead>
|
||||
<tr>
|
||||
{{# daynames }}
|
||||
<th class="header text-xs-center" aria-label="{{fullname}}">
|
||||
{{shortname}}
|
||||
<th class="header text-xs-center">
|
||||
<span class="sr-only">{{fullname}}</span>
|
||||
<span aria-hidden="true">{{shortname}}</span>
|
||||
</th>
|
||||
{{/ daynames }}
|
||||
</tr>
|
||||
@@ -71,6 +72,7 @@
|
||||
data-region="day"
|
||||
data-new-event-timestamp="{{neweventtimestamp}}">
|
||||
<div class="d-none d-md-block hidden-phone text-xs-center">
|
||||
<span class="sr-only">{{daytitle}}</span>
|
||||
{{#hasevents}}
|
||||
<a data-action="view-day-link" href="#" class="day" aria-label="{{viewdaylinktitle}}"
|
||||
data-year="{{date.year}}" data-month="{{date.mon}}" data-day="{{mday}}"
|
||||
@@ -78,7 +80,7 @@
|
||||
data-timestamp="{{timestamp}}">{{mday}}</a>
|
||||
{{/hasevents}}
|
||||
{{^hasevents}}
|
||||
{{mday}}
|
||||
<span aria-hidden="true">{{mday}}</span>
|
||||
{{/hasevents}}
|
||||
{{#hasevents}}
|
||||
<div data-region="day-content">
|
||||
@@ -122,6 +124,7 @@
|
||||
{{/hasevents}}
|
||||
</div>
|
||||
<div class="d-md-none hidden-desktop hidden-tablet">
|
||||
<span class="sr-only">{{daytitle}}</span>
|
||||
{{#hasevents}}
|
||||
<a data-action="view-day-link" href="#" class="day" aria-label="{{viewdaylinktitle}}"
|
||||
data-year="{{date.year}}" data-month="{{date.mon}}" data-day="{{mday}}"
|
||||
@@ -129,7 +132,7 @@
|
||||
data-timestamp="{{timestamp}}">{{mday}}</a>
|
||||
{{/hasevents}}
|
||||
{{^hasevents}}
|
||||
{{mday}}
|
||||
<span aria-hidden="true">{{mday}}</span>
|
||||
{{/hasevents}}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -78,8 +78,9 @@
|
||||
<thead>
|
||||
<tr>
|
||||
{{# daynames }}
|
||||
<th class="header text-xs-center" scope="col" aria-label="{{fullname}}">
|
||||
{{shortname}}
|
||||
<th class="header text-xs-center">
|
||||
<span class="sr-only">{{fullname}}</span>
|
||||
<span aria-hidden="true">{{shortname}}</span>
|
||||
</th>
|
||||
{{/ daynames }}
|
||||
</tr>
|
||||
@@ -128,7 +129,8 @@
|
||||
This is the timestamp for this month.
|
||||
}} data-day-timestamp="{{timestamp}}"{{!
|
||||
}}>{{!
|
||||
}}{{#popovertitle}}
|
||||
}}<span class="sr-only">{{daytitle}}</span>
|
||||
{{#popovertitle}}
|
||||
{{< core_calendar/minicalendar_day_link }}
|
||||
{{$day}}{{mday}}{{/day}}
|
||||
{{$url}}{{viewdaylink}}{{/url}}
|
||||
@@ -145,7 +147,7 @@
|
||||
{{/ core_calendar/minicalendar_day_link }}
|
||||
{{/popovertitle}}{{!
|
||||
}}{{^popovertitle}}
|
||||
{{mday}}
|
||||
<span aria-hidden="true">{{mday}}</span>
|
||||
{{/popovertitle}}{{!
|
||||
}}</td>
|
||||
{{/days}}
|
||||
|
||||
@@ -121,7 +121,7 @@ class behat_calendar extends behat_base {
|
||||
*/
|
||||
public function i_view_the_calendar_for($month, $year) {
|
||||
$time = make_timestamp($year, $month, 1);
|
||||
$this->getSession()->visit($this->locate_path('/calendar/view.php?view=month&course=1&time='.$time));
|
||||
$this->execute('behat_general::i_visit', ['/calendar/view.php?view=month&course=1&time='.$time]);
|
||||
|
||||
}
|
||||
|
||||
@@ -134,6 +134,6 @@ class behat_calendar extends behat_base {
|
||||
*/
|
||||
public function i_am_viewing_site_calendar() {
|
||||
$url = new moodle_url('/calendar/view.php', ['view' => 'month']);
|
||||
$this->getSession()->visit($this->locate_path($url->out_as_local_url(false)));
|
||||
$this->execute('behat_general::i_visit', [$url]);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@ foreach($cohorts['cohorts'] as $cohort) {
|
||||
$cohortmanager = has_capability('moodle/cohort:manage', $cohortcontext);
|
||||
$cohortcanassign = has_capability('moodle/cohort:assign', $cohortcontext);
|
||||
|
||||
$urlparams = array('id' => $cohort->id, 'returnurl' => $baseurl->out_as_local_url());
|
||||
$urlparams = array('id' => $cohort->id, 'returnurl' => $baseurl->out_as_local_url(false));
|
||||
$showhideurl = new moodle_url('/cohort/edit.php', $urlparams + array('sesskey' => sesskey()));
|
||||
if ($cohortmanager) {
|
||||
if ($cohort->visible) {
|
||||
|
||||
@@ -337,7 +337,7 @@ class core_comment_external extends external_api {
|
||||
$args->area = $commentrecord->commentarea;
|
||||
$manager = new comment($args);
|
||||
|
||||
if ($commentrecord->userid != $USER->id && !$manager->can_delete($commentrecord->id)) {
|
||||
if (!$manager->can_delete($commentrecord)) {
|
||||
throw new comment_exception('nopermissiontodelentry');
|
||||
}
|
||||
|
||||
|
||||
@@ -91,8 +91,8 @@ switch ($action) {
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
$comment_record = $DB->get_record('comments', array('id'=>$commentid));
|
||||
if ($manager->can_delete($commentid) || $comment_record->userid == $USER->id) {
|
||||
$comment = $DB->get_record('comments', ['id' => $commentid]);
|
||||
if ($manager->can_delete($comment)) {
|
||||
if ($manager->delete($commentid)) {
|
||||
$result = array(
|
||||
'client_id' => $client_id,
|
||||
|
||||
+39
-12
@@ -589,8 +589,7 @@ class comment {
|
||||
$c->avatar = $OUTPUT->user_picture($u, array('size'=>18));
|
||||
$c->userid = $u->id;
|
||||
|
||||
$candelete = $this->can_delete($c->id);
|
||||
if (($USER->id == $u->id) || !empty($candelete)) {
|
||||
if ($this->can_delete($c)) {
|
||||
$c->delete = true;
|
||||
}
|
||||
$comments[] = $c;
|
||||
@@ -800,16 +799,22 @@ class comment {
|
||||
/**
|
||||
* Delete a comment
|
||||
*
|
||||
* @param int $commentid
|
||||
* @param int|stdClass $comment The id of a comment, or a comment record.
|
||||
* @return bool
|
||||
*/
|
||||
public function delete($commentid) {
|
||||
global $DB, $USER;
|
||||
$candelete = has_capability('moodle/comment:delete', $this->context);
|
||||
if (!$comment = $DB->get_record('comments', array('id'=>$commentid))) {
|
||||
public function delete($comment) {
|
||||
global $DB;
|
||||
if (is_object($comment)) {
|
||||
$commentid = $comment->id;
|
||||
} else {
|
||||
$commentid = $comment;
|
||||
$comment = $DB->get_record('comments', ['id' => $commentid]);
|
||||
}
|
||||
|
||||
if (!$comment) {
|
||||
throw new comment_exception('dbupdatefailed');
|
||||
}
|
||||
if (!($USER->id == $comment->userid || !empty($candelete))) {
|
||||
if (!$this->can_delete($comment)) {
|
||||
throw new comment_exception('nopermissiontocomment');
|
||||
}
|
||||
$DB->delete_records('comments', array('id'=>$commentid));
|
||||
@@ -976,13 +981,35 @@ class comment {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the user can delete this comment
|
||||
* @param int $commentid
|
||||
* Returns true if the user can delete this comment.
|
||||
*
|
||||
* The user can delete comments if it is one they posted and they can still make posts,
|
||||
* or they have the capability to delete comments.
|
||||
*
|
||||
* A database call is avoided if a comment record is passed.
|
||||
*
|
||||
* @param int|stdClass $comment The id of a comment, or a comment record.
|
||||
* @return bool
|
||||
*/
|
||||
public function can_delete($commentid) {
|
||||
public function can_delete($comment) {
|
||||
global $USER, $DB;
|
||||
if (is_object($comment)) {
|
||||
$commentid = $comment->id;
|
||||
} else {
|
||||
$commentid = $comment;
|
||||
}
|
||||
|
||||
$this->validate(array('commentid'=>$commentid));
|
||||
return has_capability('moodle/comment:delete', $this->context);
|
||||
|
||||
if (!is_object($comment)) {
|
||||
// Get the comment record from the database.
|
||||
$comment = $DB->get_record('comments', array('id' => $commentid), 'id, userid', MUST_EXIST);
|
||||
}
|
||||
|
||||
$hascapability = has_capability('moodle/comment:delete', $this->context);
|
||||
$owncomment = $USER->id == $comment->userid;
|
||||
|
||||
return ($hascapability || ($owncomment && $this->can_post()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Tests for comments when the context is frozen.
|
||||
*
|
||||
* @package core_comment
|
||||
* @copyright 2019 University of Nottingham
|
||||
* @author Neill Magill <neill.magill@nottingham.ac.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Tests for comments when the context is frozen.
|
||||
*
|
||||
* @package core_comment
|
||||
* @copyright 2019 University of Nottingham
|
||||
* @author Neill Magill <neill.magill@nottingham.ac.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class comment_context_freeze_testcase extends advanced_testcase {
|
||||
/**
|
||||
* Creates a comment by a student.
|
||||
*
|
||||
* Returns:
|
||||
* - The comment object
|
||||
* - The sudent that wrote the comment
|
||||
* - The arguments used to create the comment
|
||||
*
|
||||
* @param stdClass $course Moodle course from the datagenerator
|
||||
* @return array
|
||||
*/
|
||||
protected function create_student_comment_and_freeze_course($course): array {
|
||||
set_config('contextlocking', 1);
|
||||
|
||||
$context = context_course::instance($course->id);
|
||||
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
|
||||
|
||||
$args = new stdClass;
|
||||
$args->context = $context;
|
||||
$args->course = $course;
|
||||
$args->area = 'page_comments';
|
||||
$args->itemid = 0;
|
||||
$args->component = 'block_comments';
|
||||
$args->linktext = get_string('showcomments');
|
||||
$args->notoggle = true;
|
||||
$args->autostart = true;
|
||||
$args->displaycancel = false;
|
||||
|
||||
// Create a comment by the student.
|
||||
$this->setUser($student);
|
||||
$comment = new comment($args);
|
||||
$newcomment = $comment->add('New comment');
|
||||
|
||||
// Freeze the context.
|
||||
$this->setAdminUser();
|
||||
$context->set_locked(true);
|
||||
|
||||
return [$newcomment, $student, $args];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a student cannot delete their own comments in frozen contexts via the external service.
|
||||
*/
|
||||
public function test_delete_student_external() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/comment/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
|
||||
|
||||
// Check that a student cannot delete their own comment.
|
||||
$this->setUser($student);
|
||||
$studentcomment = new comment($args);
|
||||
$this->assertFalse($studentcomment->can_delete($newcomment->id));
|
||||
$this->assertFalse($studentcomment->can_post());
|
||||
$this->expectException(comment_exception::class);
|
||||
$this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
|
||||
core_comment_external::delete_comments([$newcomment->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a student cannot delete their own comments in frozen contexts.
|
||||
*/
|
||||
public function test_delete_student() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/comment/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
|
||||
|
||||
// Check that a student cannot delete their own comment.
|
||||
$this->setUser($student);
|
||||
$studentcomment = new comment($args);
|
||||
$this->assertFalse($studentcomment->can_delete($newcomment->id));
|
||||
$this->assertFalse($studentcomment->can_post());
|
||||
$this->expectException(comment_exception::class);
|
||||
$this->expectExceptionMessage(get_string('nopermissiontocomment', 'error'));
|
||||
$studentcomment->delete($newcomment->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an admin cannot delete comments in frozen contexts via the external service.
|
||||
*/
|
||||
public function test_delete_admin_external() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/comment/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
|
||||
|
||||
// Check that the admin user cannot delete the comment.
|
||||
$admincomment = new comment($args);
|
||||
$this->assertFalse($admincomment->can_delete($newcomment->id));
|
||||
$this->assertFalse($admincomment->can_post());
|
||||
$this->expectException(comment_exception::class);
|
||||
$this->expectExceptionMessage(get_string('nopermissiontodelentry', 'error'));
|
||||
core_comment_external::delete_comments([$newcomment->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an admin cannot delete comments in frozen contexts.
|
||||
*/
|
||||
public function test_delete_admin() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/comment/lib.php');
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
|
||||
list($newcomment, $student, $args) = $this->create_student_comment_and_freeze_course($course);
|
||||
|
||||
// Check that the admin user cannot delete the comment.
|
||||
$admincomment = new comment($args);
|
||||
$this->assertFalse($admincomment->can_delete($newcomment->id));
|
||||
$this->assertFalse($admincomment->can_post());
|
||||
$this->expectException(comment_exception::class);
|
||||
$this->expectExceptionMessage(get_string('nopermissiontocomment', 'error'));
|
||||
$admincomment->delete($newcomment->id);
|
||||
}
|
||||
}
|
||||
@@ -4660,6 +4660,9 @@ class api {
|
||||
$recommend = false;
|
||||
$strdesc = 'evidence_coursemodulecompleted';
|
||||
|
||||
if ($outcome == course_module_competency::OUTCOME_NONE) {
|
||||
continue;
|
||||
}
|
||||
if ($outcome == course_module_competency::OUTCOME_EVIDENCE) {
|
||||
$action = evidence::ACTION_LOG;
|
||||
|
||||
@@ -4720,6 +4723,9 @@ class api {
|
||||
$recommend = false;
|
||||
$strdesc = 'evidence_coursecompleted';
|
||||
|
||||
if ($outcome == course_module_competency::OUTCOME_NONE) {
|
||||
continue;
|
||||
}
|
||||
if ($outcome == course_competency::OUTCOME_EVIDENCE) {
|
||||
$action = evidence::ACTION_LOG;
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
@core @core_completion
|
||||
Feature: Set completion of other courses as criteria for completion of current course
|
||||
In order to set completion of other courses as criteria for completion of current course
|
||||
As a user
|
||||
I want to select the prerequisite courses in completion settings
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category | enablecompletion |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
| Course 2 | C2 | 0 | 1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Student | One | student1@example.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
|
||||
@javascript
|
||||
Scenario: Set completion of prerequisite course as completion criteria of current course
|
||||
When I log in as "admin"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I navigate to "Course completion" in current page administration
|
||||
And I click on "Condition: Completion of other courses" "link"
|
||||
And I set the field "Courses available" to "Course 2"
|
||||
And I press "Save changes"
|
||||
And I add the "Course completion status" block
|
||||
And I click on "View course report" "link" in the "Course completion status" "block"
|
||||
Then I should see "Course 2" in the "completion-progress" "table"
|
||||
And I should see "Student One" in the "completion-progress" "table"
|
||||
@@ -75,6 +75,7 @@ class core_course_editcategory_form extends moodleform {
|
||||
|
||||
$mform->addElement('editor', 'description_editor', get_string('description'), null,
|
||||
$this->get_description_editor_options());
|
||||
$mform->setType('description_editor', PARAM_RAW);
|
||||
|
||||
if (!empty($CFG->allowcategorythemes)) {
|
||||
$themes = array(''=>get_string('forceno'));
|
||||
@@ -105,7 +106,8 @@ class core_course_editcategory_form extends moodleform {
|
||||
return array(
|
||||
'maxfiles' => EDITOR_UNLIMITED_FILES,
|
||||
'maxbytes' => $CFG->maxbytes,
|
||||
'trusttext' => true,
|
||||
'trusttext' => false,
|
||||
'noclean' => true,
|
||||
'context' => $context,
|
||||
'subdirs' => file_area_contains_subdirs($context, 'coursecat', 'description', $itemid),
|
||||
);
|
||||
|
||||
@@ -201,13 +201,13 @@ class helper {
|
||||
if ($category->can_change_sortorder()) {
|
||||
$actions['moveup'] = array(
|
||||
'url' => new \moodle_url($baseurl, array('action' => 'movecategoryup')),
|
||||
'icon' => new \pix_icon('t/up', new \lang_string('up')),
|
||||
'string' => new \lang_string('up')
|
||||
'icon' => new \pix_icon('t/up', new \lang_string('moveup')),
|
||||
'string' => new \lang_string('moveup')
|
||||
);
|
||||
$actions['movedown'] = array(
|
||||
'url' => new \moodle_url($baseurl, array('action' => 'movecategorydown')),
|
||||
'icon' => new \pix_icon('t/down', new \lang_string('down')),
|
||||
'string' => new \lang_string('down')
|
||||
'icon' => new \pix_icon('t/down', new \lang_string('movedown')),
|
||||
'string' => new \lang_string('movedown')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ class helper {
|
||||
*
|
||||
* @param \core_course_category $category
|
||||
* @param \core_course_list_element $course
|
||||
* @return string
|
||||
* @return array
|
||||
*/
|
||||
public static function get_course_listitem_actions(\core_course_category $category, \core_course_list_element $course) {
|
||||
$baseurl = new \moodle_url(
|
||||
@@ -400,12 +400,12 @@ class helper {
|
||||
if ($category->can_resort_courses()) {
|
||||
$actions[] = array(
|
||||
'url' => new \moodle_url($baseurl, array('action' => 'movecourseup')),
|
||||
'icon' => new \pix_icon('t/up', \get_string('up')),
|
||||
'icon' => new \pix_icon('t/up', \get_string('moveup')),
|
||||
'attributes' => array('data-action' => 'moveup', 'class' => 'action-moveup')
|
||||
);
|
||||
$actions[] = array(
|
||||
'url' => new \moodle_url($baseurl, array('action' => 'movecoursedown')),
|
||||
'icon' => new \pix_icon('t/down', \get_string('down')),
|
||||
'icon' => new \pix_icon('t/down', \get_string('movedown')),
|
||||
'attributes' => array('data-action' => 'movedown', 'class' => 'action-movedown')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ class core_course_management_renderer extends plugin_renderer_base {
|
||||
$categoryid = '';
|
||||
}
|
||||
$select = new single_select($this->page->url, 'categoryid', $categories, $categoryid, $nothing);
|
||||
$select->attributes['aria-label'] = get_string('selectacategory');
|
||||
$html .= $this->render($select);
|
||||
}
|
||||
$html .= html_writer::end_div();
|
||||
@@ -264,8 +265,8 @@ class core_course_management_renderer extends plugin_renderer_base {
|
||||
$html .= html_writer::start_div('float-left ' . $checkboxclass);
|
||||
$html .= html_writer::start_div('custom-control custom-checkbox mr-1 ');
|
||||
$html .= html_writer::empty_tag('input', $bcatinput);
|
||||
$html .= html_writer::tag('label', '', array(
|
||||
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
|
||||
$labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
|
||||
$html .= html_writer::tag('label', $labeltext, array(
|
||||
'class' => 'custom-control-label',
|
||||
'for' => 'categorylistitem' . $category->id));
|
||||
$html .= html_writer::end_div();
|
||||
@@ -542,7 +543,7 @@ class core_course_management_renderer extends plugin_renderer_base {
|
||||
$html .= html_writer::start_div('card-body');
|
||||
$html .= $this->course_listing_actions($category, $course, $perpage);
|
||||
$html .= $this->listing_pagination($category, $page, $perpage, false, $viewmode);
|
||||
$html .= html_writer::start_tag('ul', array('class' => 'ml course-list', 'role' => 'group'));
|
||||
$html .= html_writer::start_tag('ul', array('class' => 'ml course-list'));
|
||||
foreach ($category->get_courses($options) as $listitem) {
|
||||
$html .= $this->course_listitem($category, $listitem, $courseid);
|
||||
}
|
||||
@@ -643,8 +644,8 @@ class core_course_management_renderer extends plugin_renderer_base {
|
||||
$html .= html_writer::start_div('float-left ' . $checkboxclass);
|
||||
$html .= html_writer::start_div('custom-control custom-checkbox mr-1 ');
|
||||
$html .= html_writer::empty_tag('input', $bulkcourseinput);
|
||||
$html .= html_writer::tag('label', '', array(
|
||||
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
|
||||
$labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
|
||||
$html .= html_writer::tag('label', $labeltext, array(
|
||||
'class' => 'custom-control-label',
|
||||
'for' => 'courselistitem' . $course->id));
|
||||
$html .= html_writer::end_div();
|
||||
@@ -1217,8 +1218,8 @@ class core_course_management_renderer extends plugin_renderer_base {
|
||||
if ($bulkcourseinput) {
|
||||
$html .= html_writer::start_div('custom-control custom-checkbox mr-1');
|
||||
$html .= html_writer::empty_tag('input', $bulkcourseinput);
|
||||
$html .= html_writer::tag('label', '', array(
|
||||
'aria-label' => get_string('bulkactionselect', 'moodle', $text),
|
||||
$labeltext = html_writer::span(get_string('bulkactionselect', 'moodle', $text), 'sr-only');
|
||||
$html .= html_writer::tag('label', $labeltext, array(
|
||||
'class' => 'custom-control-label',
|
||||
'for' => 'coursesearchlistitem' . $course->id));
|
||||
$html .= html_writer::end_div();
|
||||
@@ -1325,12 +1326,12 @@ class core_course_management_renderer extends plugin_renderer_base {
|
||||
$output .= html_writer::start_tag('form', array('class' => 'card', 'id' => $formid,
|
||||
'action' => $searchurl, 'method' => 'get'));
|
||||
$output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset'));
|
||||
$output .= html_writer::tag('div', $this->output->heading($strsearchcourses.': ', 2, 'm-0'),
|
||||
$output .= html_writer::tag('legend', $this->output->heading($strsearchcourses.': ', 2, 'm-0'),
|
||||
array('class' => 'card-header'));
|
||||
$output .= html_writer::start_div('card-body');
|
||||
$output .= html_writer::start_div('input-group col-sm-6 col-lg-4 m-auto');
|
||||
$output .= html_writer::empty_tag('input', array('class' => 'form-control', 'type' => 'text', 'id' => $inputid,
|
||||
'size' => $inputsize, 'name' => 'search', 'value' => s($value)));
|
||||
'size' => $inputsize, 'name' => 'search', 'value' => s($value), 'aria-label' => get_string('searchcourses')));
|
||||
$output .= html_writer::start_tag('span', array('class' => 'input-group-btn'));
|
||||
$output .= html_writer::tag('button', get_string('go'), array('class' => 'btn btn-primary', 'type' => 'submit'));
|
||||
$output .= html_writer::end_tag('span');
|
||||
|
||||
@@ -128,14 +128,16 @@ class course_completion_form extends moodleform {
|
||||
}
|
||||
|
||||
// Get applicable courses (prerequisites).
|
||||
$selectedcourses = $DB->get_fieldset_sql("SELECT cc.courseinstance
|
||||
FROM {course_completion_criteria} cc WHERE cc.course = ?", [$course->id]);
|
||||
$hasselectablecourses = core_course_category::search_courses(['onlywithcompletion' => true], ['limit' => 2]);
|
||||
unset($hasselectablecourses[$course->id]);
|
||||
if ($hasselectablecourses) {
|
||||
// Show multiselect box.
|
||||
$mform->addElement('course', 'criteria_course', get_string('coursesavailable', 'completion'),
|
||||
array('multiple' => 'multiple', 'onlywithcompletion' => true, 'exclude' => $course->id));
|
||||
$mform->setType('criteria_course', PARAM_INT);
|
||||
|
||||
$selectedcourses = $DB->get_fieldset_select('course_completion_criteria', 'courseinstance',
|
||||
'course = :course AND criteriatype = :type', ['course' => $course->id, 'type' => COMPLETION_CRITERIA_TYPE_COURSE]);
|
||||
$mform->setDefault('criteria_course', $selectedcourses);
|
||||
|
||||
// Map aggregation methods to context-sensitive human readable dropdown menu.
|
||||
|
||||
+15
-2
@@ -855,6 +855,13 @@ class core_course_external extends external_api {
|
||||
}
|
||||
require_capability('moodle/course:create', $context);
|
||||
|
||||
// Fullname and short name are required to be non-empty.
|
||||
if (trim($course['fullname']) === '') {
|
||||
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
|
||||
} else if (trim($course['shortname']) === '') {
|
||||
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
|
||||
}
|
||||
|
||||
// Make sure lang is valid
|
||||
if (array_key_exists('lang', $course)) {
|
||||
if (empty($availablelangs[$course['lang']])) {
|
||||
@@ -1040,14 +1047,20 @@ class core_course_external extends external_api {
|
||||
$course['category'] = $course['categoryid'];
|
||||
}
|
||||
|
||||
// Check if the user can change fullname.
|
||||
// Check if the user can change fullname, and the new value is non-empty.
|
||||
if (array_key_exists('fullname', $course) && ($oldcourse->fullname != $course['fullname'])) {
|
||||
require_capability('moodle/course:changefullname', $context);
|
||||
if (trim($course['fullname']) === '') {
|
||||
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'fullname');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the user can change shortname.
|
||||
// Check if the user can change shortname, and the new value is non-empty.
|
||||
if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) {
|
||||
require_capability('moodle/course:changeshortname', $context);
|
||||
if (trim($course['shortname']) === '') {
|
||||
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'shortname');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the user can change the idnumber.
|
||||
|
||||
@@ -48,8 +48,8 @@ class restore_format_topics_plugin extends restore_format_plugin {
|
||||
*/
|
||||
protected function need_restore_numsections() {
|
||||
$backupinfo = $this->step->get_task()->get_info();
|
||||
$backuprelease = $backupinfo->backup_release;
|
||||
return version_compare($backuprelease, '3.3', 'lt');
|
||||
$backuprelease = $backupinfo->backup_release; // The major version: 2.9, 3.0, 3.10...
|
||||
return version_compare($backuprelease, '3.3', '<');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,8 +48,8 @@ class restore_format_weeks_plugin extends restore_format_plugin {
|
||||
*/
|
||||
protected function is_pre_33_backup() {
|
||||
$backupinfo = $this->step->get_task()->get_info();
|
||||
$backuprelease = $backupinfo->backup_release;
|
||||
return version_compare($backuprelease, '3.3', 'lt');
|
||||
$backuprelease = $backupinfo->backup_release; // The major version: 2.9, 3.0, 3.10...
|
||||
return version_compare($backuprelease, '3.3', '<');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-9
@@ -733,20 +733,14 @@ function get_category_or_system_context($categoryid) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns full course categories trees to be used in html_writer::select()
|
||||
* Returns the list of full course categories to be used in html_writer::select()
|
||||
*
|
||||
* Calls {@link core_course_category::make_categories_list()} to build the tree and
|
||||
* adds whitespace to denote nesting
|
||||
* Calls {@see core_course_category::make_categories_list()} to build the list.
|
||||
*
|
||||
* @return array array mapping course category id to the display name
|
||||
*/
|
||||
function make_categories_options() {
|
||||
$cats = core_course_category::make_categories_list('', 0, ' / ');
|
||||
foreach ($cats as $key => $value) {
|
||||
// Prefix the value with the number of spaces equal to category depth (number of separators in the value).
|
||||
$cats[$key] = str_repeat(' ', substr_count($value, ' / ')). $value;
|
||||
}
|
||||
return $cats;
|
||||
return core_course_category::make_categories_list('', 0, ' / ');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-1
@@ -143,7 +143,12 @@ $mform->set_data($data);
|
||||
|
||||
if ($mform->is_cancelled()) {
|
||||
if ($return && !empty($cm->id)) {
|
||||
redirect("$CFG->wwwroot/mod/$module->name/view.php?id=$cm->id");
|
||||
$urlparams = [
|
||||
'id' => $cm->id, // We always need the activity id.
|
||||
'forceview' => 1, // Stop file downloads in resources.
|
||||
];
|
||||
$activityurl = new moodle_url("/mod/$module->name/view.php", $urlparams);
|
||||
redirect($activityurl);
|
||||
} else {
|
||||
redirect(course_get_url($course, $cw->section, array('sr' => $sectionreturn)));
|
||||
}
|
||||
|
||||
@@ -551,6 +551,80 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
$createdsubcats = core_course_external::create_courses($courses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testing empty fields produce expected exceptions
|
||||
*
|
||||
* @see test_create_courses_empty_field
|
||||
* @see test_update_courses_empty_field
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function course_empty_field_provider(): array {
|
||||
return [
|
||||
[[
|
||||
'fullname' => '',
|
||||
'shortname' => 'ws101',
|
||||
], 'fullname'],
|
||||
[[
|
||||
'fullname' => ' ',
|
||||
'shortname' => 'ws101',
|
||||
], 'fullname'],
|
||||
[[
|
||||
'fullname' => 'Web Services',
|
||||
'shortname' => '',
|
||||
], 'shortname'],
|
||||
[[
|
||||
'fullname' => 'Web Services',
|
||||
'shortname' => ' ',
|
||||
], 'shortname'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test creating courses with empty fields throws an exception
|
||||
*
|
||||
* @param array $course
|
||||
* @param string $expectedemptyfield
|
||||
*
|
||||
* @dataProvider course_empty_field_provider
|
||||
*/
|
||||
public function test_create_courses_empty_field(array $course, string $expectedemptyfield): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
// Create a category for the new course.
|
||||
$course['categoryid'] = $this->getDataGenerator()->create_category()->id;
|
||||
|
||||
$this->expectException(moodle_exception::class);
|
||||
$this->expectExceptionMessageRegExp("/{$expectedemptyfield}/");
|
||||
core_course_external::create_courses([$course]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updating courses with empty fields returns warnings
|
||||
*
|
||||
* @param array $course
|
||||
* @param string $expectedemptyfield
|
||||
*
|
||||
* @dataProvider course_empty_field_provider
|
||||
*/
|
||||
public function test_update_courses_empty_field(array $course, string $expectedemptyfield): void {
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
// Create a course to update.
|
||||
$course['id'] = $this->getDataGenerator()->create_course()->id;
|
||||
|
||||
$result = core_course_external::update_courses([$course]);
|
||||
$result = core_course_external::clean_returnvalue(core_course_external::update_courses_returns(), $result);
|
||||
|
||||
$this->assertCount(1, $result['warnings']);
|
||||
|
||||
$warning = reset($result['warnings']);
|
||||
$this->assertEquals('errorinvalidparam', $warning['warningcode']);
|
||||
$this->assertContains($expectedemptyfield, $warning['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test delete_courses
|
||||
*/
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 372 B |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" preserveAspectRatio="xMinYMid meet">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(60%,60%,60%);fill-opacity:1;" d="M 13.144531 8.304688 L 13.144531 11.144531 C 13.144531 11.851562 12.890625 12.457031 12.386719 12.960938 C 11.886719 13.460938 11.28125 13.714844 10.570312 13.714844 L 3.144531 13.714844 C 2.433594 13.714844 1.828125 13.460938 1.324219 12.960938 C 0.824219 12.457031 0.570312 11.851562 0.570312 11.144531 L 0.570312 3.714844 C 0.570312 3.007812 0.824219 2.398438 1.324219 1.898438 C 1.828125 1.394531 2.433594 1.144531 3.144531 1.144531 L 10.570312 1.144531 C 10.945312 1.144531 11.292969 1.21875 11.617188 1.367188 C 11.707031 1.40625 11.757812 1.476562 11.777344 1.570312 C 11.792969 1.671875 11.769531 1.757812 11.695312 1.832031 L 11.257812 2.269531 C 11.199219 2.328125 11.132812 2.355469 11.054688 2.355469 C 11.035156 2.355469 11.007812 2.351562 10.972656 2.339844 C 10.835938 2.304688 10.703125 2.285156 10.570312 2.285156 L 3.144531 2.285156 C 2.75 2.285156 2.414062 2.425781 2.132812 2.707031 C 1.855469 2.984375 1.714844 3.320312 1.714844 3.714844 L 1.714844 11.144531 C 1.714844 11.535156 1.855469 11.871094 2.132812 12.152344 C 2.414062 12.429688 2.75 12.570312 3.144531 12.570312 L 10.570312 12.570312 C 10.964844 12.570312 11.300781 12.429688 11.582031 12.152344 C 11.859375 11.871094 12 11.535156 12 11.144531 L 12 8.875 C 12 8.796875 12.027344 8.730469 12.082031 8.679688 L 12.652344 8.105469 C 12.710938 8.046875 12.78125 8.019531 12.855469 8.019531 C 12.894531 8.019531 12.929688 8.027344 12.964844 8.042969 C 13.082031 8.09375 13.144531 8.179688 13.144531 8.304688 Z M 15.207031 3.9375 L 7.9375 11.207031 C 7.792969 11.347656 7.625 11.417969 7.429688 11.417969 C 7.230469 11.417969 7.0625 11.347656 6.917969 11.207031 L 3.082031 7.367188 C 2.9375 7.222656 2.867188 7.054688 2.867188 6.855469 C 2.867188 6.660156 2.9375 6.492188 3.082031 6.347656 L 4.0625 5.367188 C 4.207031 5.222656 4.375 5.152344 4.570312 5.152344 C 4.769531 5.152344 4.9375 5.222656 5.082031 5.367188 L 7.429688 7.714844 L 13.207031 1.9375 C 13.347656 1.792969 13.519531 1.722656 13.714844 1.722656 C 13.910156 1.722656 14.082031 1.792969 14.222656 1.9375 L 15.207031 2.917969 C 15.347656 3.0625 15.417969 3.230469 15.417969 3.429688 C 15.417969 3.625 15.347656 3.792969 15.207031 3.9375 Z M 15.207031 3.9375 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 266 B |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" preserveAspectRatio="xMinYMid meet">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(60%,60%,60%);fill-opacity:1;" d="M 11.714844 2.285156 L 4.285156 2.285156 C 3.894531 2.285156 3.554688 2.425781 3.277344 2.707031 C 2.996094 2.984375 2.855469 3.320312 2.855469 3.714844 L 2.855469 11.144531 C 2.855469 11.535156 2.996094 11.871094 3.277344 12.152344 C 3.554688 12.429688 3.894531 12.570312 4.285156 12.570312 L 11.714844 12.570312 C 12.105469 12.570312 12.445312 12.429688 12.722656 12.152344 C 13.003906 11.871094 13.144531 11.535156 13.144531 11.144531 L 13.144531 3.714844 C 13.144531 3.320312 13.003906 2.984375 12.722656 2.707031 C 12.445312 2.425781 12.105469 2.285156 11.714844 2.285156 Z M 14.285156 3.714844 L 14.285156 11.144531 C 14.285156 11.851562 14.035156 12.457031 13.53125 12.960938 C 13.027344 13.460938 12.421875 13.714844 11.714844 13.714844 L 4.285156 13.714844 C 3.578125 13.714844 2.972656 13.460938 2.46875 12.960938 C 1.964844 12.457031 1.714844 11.851562 1.714844 11.144531 L 1.714844 3.714844 C 1.714844 3.007812 1.964844 2.398438 2.46875 1.898438 C 2.972656 1.394531 3.578125 1.144531 4.285156 1.144531 L 11.714844 1.144531 C 12.421875 1.144531 13.027344 1.394531 13.53125 1.898438 C 14.035156 2.398438 14.285156 3.007812 14.285156 3.714844 Z M 14.285156 3.714844 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -96,7 +96,7 @@ class data_controller extends \core_customfield\data_controller {
|
||||
$this->save();
|
||||
}
|
||||
|
||||
if ($fromform['text']) {
|
||||
if (array_key_exists('text', $fromform)) {
|
||||
$textoptions = $this->value_editor_options();
|
||||
$data = (object) ['field_editor' => $fromform];
|
||||
$data = file_postupdate_standard_editor($data, 'field', $textoptions, $textoptions['context'],
|
||||
|
||||
@@ -145,6 +145,41 @@ class customfield_textarea_plugin_testcase extends advanced_testcase {
|
||||
$handler->instance_form_save($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that instance form save empties the field content for blank values
|
||||
*/
|
||||
public function test_instance_form_save_clear(): void {
|
||||
global $CFG;
|
||||
|
||||
require_once("{$CFG->dirroot}/customfield/tests/fixtures/test_instance_form.php");
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
$handler = $this->cfcat->get_handler();
|
||||
|
||||
// Set our custom field to a known value.
|
||||
$submitdata = (array) $this->courses[1] + [
|
||||
'customfield_myfield1_editor' => ['text' => 'I can see it in your eyes', 'format' => FORMAT_HTML],
|
||||
'customfield_myfield2_editor' => ['text' => 'I can see it in your smile', 'format' => FORMAT_HTML],
|
||||
];
|
||||
|
||||
core_customfield_test_instance_form::mock_submit($submitdata, []);
|
||||
$form = new core_customfield_test_instance_form('post', ['handler' => $handler, 'instance' => $this->courses[1]]);
|
||||
$handler->instance_form_save($form->get_data());
|
||||
|
||||
$this->assertEquals($submitdata['customfield_myfield1_editor']['text'],
|
||||
core_customfield\data_controller::create($this->cfdata[1]->get('id'))->export_value());
|
||||
|
||||
// Now empty our non-required field.
|
||||
$submitdata['customfield_myfield1_editor']['text'] = '';
|
||||
|
||||
core_customfield_test_instance_form::mock_submit($submitdata, []);
|
||||
$form = new core_customfield_test_instance_form('post', ['handler' => $handler, 'instance' => $this->courses[1]]);
|
||||
$handler->instance_form_save($form->get_data());
|
||||
|
||||
$this->assertEmpty(core_customfield\data_controller::create($this->cfdata[1]->get('id'))->export_value());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for data_controller::get_value and export_value
|
||||
*/
|
||||
|
||||
@@ -31,83 +31,24 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* Activity name filtering
|
||||
*/
|
||||
class filter_activitynames extends moodle_text_filter {
|
||||
// Trivial-cache - keyed on $cachedcourseid and $cacheduserid.
|
||||
static $activitylist = null;
|
||||
static $cachedcourseid;
|
||||
static $cacheduserid;
|
||||
|
||||
function filter($text, array $options = array()) {
|
||||
global $USER; // Since 2.7 we can finally start using globals in filters.
|
||||
|
||||
$coursectx = $this->context->get_course_context(false);
|
||||
if (!$coursectx) {
|
||||
return $text;
|
||||
}
|
||||
$courseid = $coursectx->instanceid;
|
||||
|
||||
// Initialise/invalidate our trivial cache if dealing with a different course.
|
||||
if (!isset(self::$cachedcourseid) || self::$cachedcourseid !== (int)$courseid) {
|
||||
self::$activitylist = null;
|
||||
}
|
||||
self::$cachedcourseid = (int)$courseid;
|
||||
// And the same for user id.
|
||||
if (!isset(self::$cacheduserid) || self::$cacheduserid !== (int)$USER->id) {
|
||||
self::$activitylist = null;
|
||||
}
|
||||
self::$cacheduserid = (int)$USER->id;
|
||||
|
||||
/// It may be cached
|
||||
|
||||
if (is_null(self::$activitylist)) {
|
||||
self::$activitylist = array();
|
||||
|
||||
$modinfo = get_fast_modinfo($courseid);
|
||||
if (!empty($modinfo->cms)) {
|
||||
self::$activitylist = array(); // We will store all the created filters here.
|
||||
|
||||
// Create array of visible activities sorted by the name length (we are only interested in properties name and url).
|
||||
$sortedactivities = array();
|
||||
foreach ($modinfo->cms as $cm) {
|
||||
// Use normal access control and visibility, but exclude labels and hidden activities.
|
||||
if ($cm->visible and $cm->has_view() and $cm->uservisible) {
|
||||
$sortedactivities[] = (object)array(
|
||||
'name' => $cm->name,
|
||||
'url' => $cm->url,
|
||||
'id' => $cm->id,
|
||||
'namelen' => -strlen($cm->name), // Negative value for reverse sorting.
|
||||
);
|
||||
}
|
||||
}
|
||||
// Sort activities by the length of the activity name in reverse order.
|
||||
core_collator::asort_objects_by_property($sortedactivities, 'namelen', core_collator::SORT_NUMERIC);
|
||||
|
||||
foreach ($sortedactivities as $cm) {
|
||||
$title = s(trim(strip_tags($cm->name)));
|
||||
$currentname = trim($cm->name);
|
||||
$entitisedname = s($currentname);
|
||||
// Avoid empty or unlinkable activity names.
|
||||
if (!empty($title)) {
|
||||
$href_tag_begin = html_writer::start_tag('a',
|
||||
array('class' => 'autolink', 'title' => $title,
|
||||
'href' => $cm->url));
|
||||
self::$activitylist[$cm->id] = new filterobject($currentname, $href_tag_begin, '</a>', false, true);
|
||||
if ($currentname != $entitisedname) {
|
||||
// If name has some entity (& " < >) add that filter too. MDL-17545.
|
||||
self::$activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $href_tag_begin, '</a>', false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$activitylist = $this->get_cached_activity_list($courseid);
|
||||
|
||||
$filterslist = array();
|
||||
if (self::$activitylist) {
|
||||
if (!empty($activitylist)) {
|
||||
$cmid = $this->context->instanceid;
|
||||
if ($this->context->contextlevel == CONTEXT_MODULE && isset(self::$activitylist[$cmid])) {
|
||||
if ($this->context->contextlevel == CONTEXT_MODULE && isset($activitylist[$cmid])) {
|
||||
// remove filterobjects for the current module
|
||||
$filterslist = array_values(array_diff_key(self::$activitylist, array($cmid => 1, $cmid.'-e' => 1)));
|
||||
$filterslist = array_values(array_diff_key($activitylist, array($cmid => 1, $cmid.'-e' => 1)));
|
||||
} else {
|
||||
$filterslist = array_values(self::$activitylist);
|
||||
$filterslist = array_values($activitylist);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,4 +58,76 @@ class filter_activitynames extends moodle_text_filter {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the cached activity list for a course
|
||||
*
|
||||
* @param int $courseid id of the course
|
||||
* @return filterobject[] the activities
|
||||
*/
|
||||
protected function get_cached_activity_list($courseid) {
|
||||
global $USER;
|
||||
$cached = cache::make_from_params(cache_store::MODE_REQUEST, 'filter', 'activitynames');
|
||||
|
||||
// Return cached activity list.
|
||||
if ($cached->get('cachecourseid') == $courseid && $cached->get('cacheuserid') == $USER->id) {
|
||||
return $cached->get('activitylist');
|
||||
}
|
||||
|
||||
// Not cached yet, get activity list and set cache.
|
||||
$activitylist = $this->get_activity_list($courseid);
|
||||
$cached->set('cacheuserid', $USER->id);
|
||||
$cached->set('cachecourseid', $courseid);
|
||||
$cached->set('activitylist', $activitylist);
|
||||
return $activitylist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the activity list for a course
|
||||
*
|
||||
* @param int $courseid id of the course
|
||||
* @return filterobject[] the activities
|
||||
*/
|
||||
protected function get_activity_list($courseid) {
|
||||
$activitylist = array();
|
||||
|
||||
$modinfo = get_fast_modinfo($courseid);
|
||||
if (!empty($modinfo->cms)) {
|
||||
$activitylist = array(); // We will store all the created filters here.
|
||||
|
||||
// Create array of visible activities sorted by the name length (we are only interested in properties name and url).
|
||||
$sortedactivities = array();
|
||||
foreach ($modinfo->cms as $cm) {
|
||||
// Use normal access control and visibility, but exclude labels and hidden activities.
|
||||
if ($cm->visible and $cm->has_view() and $cm->uservisible) {
|
||||
$sortedactivities[] = (object)array(
|
||||
'name' => $cm->name,
|
||||
'url' => $cm->url,
|
||||
'id' => $cm->id,
|
||||
'namelen' => -strlen($cm->name), // Negative value for reverse sorting.
|
||||
);
|
||||
}
|
||||
}
|
||||
// Sort activities by the length of the activity name in reverse order.
|
||||
core_collator::asort_objects_by_property($sortedactivities, 'namelen', core_collator::SORT_NUMERIC);
|
||||
|
||||
foreach ($sortedactivities as $cm) {
|
||||
$title = s(trim(strip_tags($cm->name)));
|
||||
$currentname = trim($cm->name);
|
||||
$entitisedname = s($currentname);
|
||||
// Avoid empty or unlinkable activity names.
|
||||
if (!empty($title)) {
|
||||
$hreftagbegin = html_writer::start_tag('a',
|
||||
array('class' => 'autolink', 'title' => $title,
|
||||
'href' => $cm->url));
|
||||
$activitylist[$cm->id] = new filterobject($currentname, $hreftagbegin, '</a>', false, true);
|
||||
if ($currentname != $entitisedname) {
|
||||
// If name has some entity (& " < >) add that filter too. MDL-17545.
|
||||
$activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $hreftagbegin, '</a>', false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $activitylist;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,4 +102,56 @@ class filter_activitynames_filter_testcase extends advanced_testcase {
|
||||
$this->assertEquals($page->cmid, $matches[2][0]);
|
||||
$this->assertEquals($page->name, $matches[3][0]);
|
||||
}
|
||||
|
||||
public function test_cache() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
// Create a test courses.
|
||||
$course1 = $this->getDataGenerator()->create_course();
|
||||
$course2 = $this->getDataGenerator()->create_course();
|
||||
$context1 = context_course::instance($course1->id);
|
||||
$context2 = context_course::instance($course2->id);
|
||||
|
||||
// Create page 1.
|
||||
$page1 = $this->getDataGenerator()->create_module('page',
|
||||
['course' => $course1->id, 'name' => 'Test 1']);
|
||||
// Format text with page 1 in HTML.
|
||||
$html = '<p>Please read the two pages Test 1 and Test 2.</p>';
|
||||
$filtered1 = format_text($html, FORMAT_HTML, array('context' => $context1));
|
||||
// Find all the activity links in the result.
|
||||
$matches = [];
|
||||
preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered1, $matches);
|
||||
// There should be 1 link.
|
||||
$this->assertCount(1, $matches[1]);
|
||||
$this->assertEquals($page1->name, $matches[1][0]);
|
||||
|
||||
// Create page 2.
|
||||
$page2 = $this->getDataGenerator()->create_module('page',
|
||||
['course' => $course1->id, 'name' => 'Test 2']);
|
||||
// Filter the text again.
|
||||
$filtered2 = format_text($html, FORMAT_HTML, array('context' => $context1));
|
||||
// The filter result does not change due to caching.
|
||||
$this->assertEquals($filtered1, $filtered2);
|
||||
|
||||
// Change context, so that cache for course 1 is cleared.
|
||||
$filtered3 = format_text($html, FORMAT_HTML, array('context' => $context2));
|
||||
$this->assertNotEquals($filtered1, $filtered3);
|
||||
$matches = [];
|
||||
preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered3, $matches);
|
||||
// There should be no links.
|
||||
$this->assertCount(0, $matches[1]);
|
||||
|
||||
// Filter the text for course 1.
|
||||
$filtered4 = format_text($html, FORMAT_HTML, array('context' => $context1));
|
||||
// Find all the activity links in the result.
|
||||
$matches = [];
|
||||
preg_match_all('~<a class="autolink" title="([^"]*)" href="[^"]*/mod/page/view.php\?id=([0-9]+)">([^<]*)</a>~',
|
||||
$filtered4, $matches);
|
||||
// There should be 2 links.
|
||||
$this->assertCount(2, $matches[1]);
|
||||
$this->assertEquals($page1->name, $matches[1][0]);
|
||||
$this->assertEquals($page2->name, $matches[1][1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,10 +399,7 @@ class gradeimport_csv_load_data {
|
||||
case 'useridnumber':
|
||||
case 'useremail':
|
||||
case 'username':
|
||||
// Skip invalid row with blank user field.
|
||||
if (!empty($value)) {
|
||||
$this->studentid = $this->check_user_exists($value, $userfields[$mappingidentifier]);
|
||||
}
|
||||
$this->studentid = $this->check_user_exists($value, $userfields[$mappingidentifier]);
|
||||
break;
|
||||
case 'new':
|
||||
$this->import_new_grade_item($header, $key, $value);
|
||||
|
||||
@@ -164,7 +164,7 @@ class grade extends tablelike implements selectable_items, filterable_items {
|
||||
public function original_headers() {
|
||||
return array(
|
||||
'', // For filter icon.
|
||||
get_string('firstname') . ' (' . get_string('alternatename') . ') ' . get_string('lastname'),
|
||||
get_string('fullnameuser', 'core'),
|
||||
get_string('range', 'grades'),
|
||||
get_string('grade', 'grades'),
|
||||
get_string('feedback', 'grades'),
|
||||
@@ -198,8 +198,8 @@ class grade extends tablelike implements selectable_items, filterable_items {
|
||||
$lockicon = $OUTPUT->pix_icon('t/locked', 'grade is locked') . ' ';
|
||||
}
|
||||
|
||||
if (!empty($item->alternatename)) {
|
||||
$fullname = $lockicon . $item->alternatename . ' (' . $item->firstname . ') ' . $item->lastname;
|
||||
if (has_capability('moodle/site:viewfullnames', \context_course::instance($this->courseid))) {
|
||||
$fullname = $lockicon . fullname($item, true);
|
||||
} else {
|
||||
$fullname = $lockicon . fullname($item);
|
||||
}
|
||||
|
||||
@@ -161,12 +161,12 @@ class finalgrade extends grade_attribute_format implements unique_value, be_disa
|
||||
}
|
||||
|
||||
if ($errorstr) {
|
||||
$user = $DB->get_record('user', array('id' => $userid), 'id, firstname, alternatename, lastname');
|
||||
$user = get_complete_user_data('id', $userid);
|
||||
$gradestr = new stdClass;
|
||||
if (!empty($user->alternatename)) {
|
||||
$gradestr->username = $user->alternatename . ' (' . $user->firstname . ') ' . $user->lastname;
|
||||
if (has_capability('moodle/site:viewfullnames', \context_course::instance($gradeitem->courseid))) {
|
||||
$gradestr->username = fullname($user, true);
|
||||
} else {
|
||||
$gradestr->username = $user->firstname . ' ' . $user->lastname;
|
||||
$gradestr->username = fullname($user);
|
||||
}
|
||||
$gradestr->itemname = $this->grade->grade_item->get_name();
|
||||
$errorstr = get_string($errorstr, 'grades', $gradestr);
|
||||
|
||||
@@ -42,33 +42,33 @@ Feature: We can bulk insert grades for students in a course
|
||||
And I am on "Course 1" course homepage
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I follow "Single view for Test assignment one"
|
||||
Then the field "Grade for james (Student) 1" matches value "50.00"
|
||||
And the field "Override for james (Student) 1" matches value "0"
|
||||
Then the field "Grade for Student 1" matches value "50.00"
|
||||
And the field "Override for Student 1" matches value "0"
|
||||
And I set the field "Perform bulk insert" to "1"
|
||||
And I set the field "Insert value" to "1.0"
|
||||
And I press "Save"
|
||||
And I press "Continue"
|
||||
And the field "Grade for james (Student) 1" matches value "50.00"
|
||||
And the field "Override for james (Student) 1" matches value "0"
|
||||
And the field "Grade for holly (Student) 2" matches value "1.00"
|
||||
And the field "Override for holly (Student) 2" matches value "1"
|
||||
And the field "Grade for anna (Student) 3" matches value "1.00"
|
||||
And the field "Override for anna (Student) 3" matches value "1"
|
||||
And the field "Grade for zac (Student) 4" matches value "1.00"
|
||||
And the field "Override for zac (Student) 4" matches value "1"
|
||||
And the field "Grade for Student 1" matches value "50.00"
|
||||
And the field "Override for Student 1" matches value "0"
|
||||
And the field "Grade for Student 2" matches value "1.00"
|
||||
And the field "Override for Student 2" matches value "1"
|
||||
And the field "Grade for Student 3" matches value "1.00"
|
||||
And the field "Override for Student 3" matches value "1"
|
||||
And the field "Grade for Student 4" matches value "1.00"
|
||||
And the field "Override for Student 4" matches value "1"
|
||||
And I set the field "For" to "All grades"
|
||||
And I set the field "Perform bulk insert" to "1"
|
||||
And I set the field "Insert value" to "2.0"
|
||||
And I press "Save"
|
||||
And I press "Continue"
|
||||
And the field "Grade for james (Student) 1" matches value "2.00"
|
||||
And the field "Override for james (Student) 1" matches value "1"
|
||||
And the field "Grade for holly (Student) 2" matches value "2.00"
|
||||
And the field "Override for holly (Student) 2" matches value "1"
|
||||
And the field "Grade for anna (Student) 3" matches value "2.00"
|
||||
And the field "Override for anna (Student) 3" matches value "1"
|
||||
And the field "Grade for zac (Student) 4" matches value "2.00"
|
||||
And the field "Override for zac (Student) 4" matches value "1"
|
||||
And the field "Grade for Student 1" matches value "2.00"
|
||||
And the field "Override for Student 1" matches value "1"
|
||||
And the field "Grade for Student 2" matches value "2.00"
|
||||
And the field "Override for Student 2" matches value "1"
|
||||
And the field "Grade for Student 3" matches value "2.00"
|
||||
And the field "Override for Student 3" matches value "1"
|
||||
And the field "Grade for Student 4" matches value "2.00"
|
||||
And the field "Override for Student 4" matches value "1"
|
||||
|
||||
Scenario: I can bulk insert grades and check their override flags for user view.
|
||||
Given I log in as "teacher1"
|
||||
@@ -110,8 +110,8 @@ Feature: We can bulk insert grades for students in a course
|
||||
And I set the field "Perform bulk insert" to "1"
|
||||
When I set the field "Insert value" to "-1"
|
||||
And I press "Save"
|
||||
Then I should see "The grade entered for Test assignment one for james (Student) 1 is less than the minimum allowed"
|
||||
And I should see "The grade entered for Test assignment one for holly (Student) 2 is less than the minimum allowed"
|
||||
And I should see "The grade entered for Test assignment one for anna (Student) 3 is less than the minimum allowed"
|
||||
And I should see "The grade entered for Test assignment one for zac (Student) 4 is less than the minimum allowed"
|
||||
Then I should see "The grade entered for Test assignment one for Student 1 is less than the minimum allowed"
|
||||
And I should see "The grade entered for Test assignment one for Student 2 is less than the minimum allowed"
|
||||
And I should see "The grade entered for Test assignment one for Student 3 is less than the minimum allowed"
|
||||
And I should see "The grade entered for Test assignment one for Student 4 is less than the minimum allowed"
|
||||
And I should see "Grades were set for 0 items"
|
||||
|
||||
@@ -9,13 +9,13 @@ Feature: We can use Single view
|
||||
| fullname | shortname | category |
|
||||
| Course 1 | C1 | 0 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email | idnumber | alternatename |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com | t1 | fred |
|
||||
| teacher2 | No edit | 1 | teacher2@example.com | t2 | nick |
|
||||
| student1 | Student | 1 | student1@example.com | s1 | james |
|
||||
| student2 | Student | 2 | student1@example.com | s2 | holly |
|
||||
| student3 | Student | 3 | student1@example.com | s3 | anna |
|
||||
| student4 | Student | 4 | student1@example.com | s4 | zac |
|
||||
| username | firstname | lastname | email | idnumber | middlename | alternatename | firstnamephonetic | lastnamephonetic |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com | t1 | | fred | | |
|
||||
| teacher2 | No edit | 1 | teacher2@example.com | t2 | | nick | | |
|
||||
| student1 | Grainne | Beauchamp | student1@example.com | s1 | Ann | Jill | Gronya | Beecham |
|
||||
| student2 | Niamh | Cholmondely | student2@example.com | s2 | Jane | Nina | Nee | Chumlee |
|
||||
| student3 | Siobhan | Desforges | student3@example.com | s3 | Sarah | Sev | Shevon | De-forjay |
|
||||
| student4 | Student | 4 | student4@example.com | s4 | | zac | | |
|
||||
And the following "scales" exist:
|
||||
| name | scale |
|
||||
| Test Scale | Disappointing, Good, Very good, Excellent |
|
||||
@@ -50,6 +50,9 @@ Feature: We can use Single view
|
||||
| capability | permission | role | contextlevel | reference |
|
||||
| moodle/grade:edit | Allow | teacher | Course | C1 |
|
||||
| gradereport/singleview:view | Allow | teacher | Course | C1 |
|
||||
And the following config values are set as admin:
|
||||
| fullnamedisplay | firstnamephonetic,lastnamephonetic |
|
||||
| alternativefullnameformat | middlename, alternatename, firstname, lastname |
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
Given I navigate to "View > Grader report" in the course gradebook
|
||||
@@ -57,7 +60,7 @@ Feature: We can use Single view
|
||||
@javascript
|
||||
Scenario: I can update grades, add feedback and exclude grades.
|
||||
Given I navigate to "View > Single view" in the course gradebook
|
||||
And I select "Student 4" from the "Select user..." singleselect
|
||||
And I select "Student" from the "Select user..." singleselect
|
||||
And I set the field "Override for Test assignment one" to "1"
|
||||
When I set the following fields to these values:
|
||||
| Grade for Test assignment one | 10.00 |
|
||||
@@ -76,29 +79,29 @@ Feature: We can use Single view
|
||||
And the field "Grade for Test grade item" matches value "45.00"
|
||||
And the field "Grade for Course total" matches value "55.00"
|
||||
And I click on "Show grades for Test assignment three" "link"
|
||||
And I click on "Override for james (Student) 1" "checkbox"
|
||||
And I click on "Override for Ann, Jill, Grainne, Beauchamp" "checkbox"
|
||||
And I set the following fields to these values:
|
||||
| Grade for james (Student) 1 | 12.05 |
|
||||
| Feedback for james (Student) 1 | test data2 |
|
||||
And I set the field "Exclude for holly (Student) 2" to "1"
|
||||
| Grade for Ann, Jill, Grainne, Beauchamp | 12.05 |
|
||||
| Feedback for Ann, Jill, Grainne, Beauchamp | test data2 |
|
||||
And I set the field "Exclude for Jane, Nina, Niamh, Cholmondely" to "1"
|
||||
And I press "Save"
|
||||
Then I should see "Grades were set for 2 items"
|
||||
And I press "Continue"
|
||||
And the field "Grade for james (Student) 1" matches value "12.05"
|
||||
And the field "Exclude for holly (Student) 2" matches value "1"
|
||||
And the field "Grade for Ann, Jill, Grainne, Beauchamp" matches value "12.05"
|
||||
And the field "Exclude for Jane, Nina, Niamh, Cholmondely" matches value "1"
|
||||
And I select "new grade item 1" from the "Select grade item..." singleselect
|
||||
And I set the field "Grade for james (Student) 1" to "Very good"
|
||||
And I set the field "Grade for Ann, Jill, Grainne, Beauchamp" to "Very good"
|
||||
And I press "Save"
|
||||
Then I should see "Grades were set for 1 items"
|
||||
And I press "Continue"
|
||||
And the following should exist in the "generaltable" table:
|
||||
| First name (Alternate name) Surname | Grade |
|
||||
| james (Student) 1 | Very good |
|
||||
| Ann, Jill, Grainne, Beauchamp | Very good |
|
||||
And I log out
|
||||
And I log in as "teacher2"
|
||||
And I am on "Course 1" course homepage
|
||||
Given I navigate to "View > Single view" in the course gradebook
|
||||
And I select "Student 4" from the "Select user..." singleselect
|
||||
And I select "Student" from the "Select user..." singleselect
|
||||
And the "Exclude for Test assignment one" "checkbox" should be disabled
|
||||
And the "Override for Test assignment one" "checkbox" should be enabled
|
||||
|
||||
@@ -106,12 +109,12 @@ Feature: We can use Single view
|
||||
Given I follow "Single view for Test assignment one"
|
||||
Then I should see "Test assignment one"
|
||||
Then I navigate to "View > Grader report" in the course gradebook
|
||||
And I follow "Single view for Student 1"
|
||||
Then I should see "Student 1"
|
||||
And I follow "Single view for Ann, Jill, Grainne, Beauchamp"
|
||||
Then I should see "Gronya,Beecham"
|
||||
|
||||
Scenario: I can bulk update grades.
|
||||
Given I follow "Single view for Student 1"
|
||||
Then I should see "Student 1"
|
||||
Given I follow "Single view for Ann, Jill, Grainne, Beauchamp"
|
||||
Then I should see "Gronya,Beecham"
|
||||
When I set the field "For" to "All grades"
|
||||
And I set the field "Insert value" to "1.0"
|
||||
And I set the field "Perform bulk insert" to "1"
|
||||
@@ -119,12 +122,12 @@ Feature: We can use Single view
|
||||
Then I should see "Grades were set for 6 items"
|
||||
|
||||
Scenario: Navigation works in the Single view.
|
||||
Given I follow "Single view for Student 1"
|
||||
Then I should see "Student 1"
|
||||
And I follow "Student 2"
|
||||
Then I should see "Student 2"
|
||||
And I follow "Student 1"
|
||||
Then I should see "Student 1"
|
||||
Given I follow "Single view for Ann, Jill, Grainne, Beauchamp"
|
||||
Then I should see "Gronya,Beecham"
|
||||
And I follow "Nee,Chumlee"
|
||||
Then I should see "Nee,Chumlee"
|
||||
And I follow "Gronya,Beecham"
|
||||
Then I should see "Gronya,Beecham"
|
||||
And I click on "Show grades for Test assignment four" "link"
|
||||
Then I should see "Test assignment four"
|
||||
And I follow "Test assignment three"
|
||||
@@ -134,7 +137,7 @@ Feature: We can use Single view
|
||||
|
||||
Scenario: Activities are clickable only when
|
||||
it has a valid activity page.
|
||||
Given I follow "Single view for Student 1"
|
||||
Given I follow "Single view for Ann, Jill, Grainne, Beauchamp"
|
||||
And "new grade item 1" "link" should not exist in the "//tbody//tr[position()=1]//td[position()=2]" "xpath_element"
|
||||
Then "Category total" "link" should not exist in the "//tbody//tr[position()=2]//td[position()=2]" "xpath_element"
|
||||
And "Course total" "link" should not exist in the "//tbody//tr[position()=last()]//td[position()=2]" "xpath_element"
|
||||
|
||||
+32
-28
@@ -49,30 +49,37 @@ $PAGE->set_pagelayout('admin');
|
||||
|
||||
$returnurl = new moodle_url('/group/index.php', array('id'=>$id));
|
||||
|
||||
$mform_post = new groups_import_form(null, array('id'=>$id));
|
||||
$importform = new groups_import_form(null, ['id' => $id]);
|
||||
|
||||
// If a file has been uploaded, then process it
|
||||
if ($mform_post->is_cancelled()) {
|
||||
if ($importform->is_cancelled()) {
|
||||
redirect($returnurl);
|
||||
|
||||
} else if ($mform_post->get_data()) {
|
||||
} else if ($formdata = $importform->get_data()) {
|
||||
echo $OUTPUT->header();
|
||||
|
||||
$csv_encode = '/\&\#44/';
|
||||
if (isset($CFG->CSV_DELIMITER)) {
|
||||
$csv_delimiter = $CFG->CSV_DELIMITER;
|
||||
|
||||
if (isset($CFG->CSV_ENCODE)) {
|
||||
$csv_encode = '/\&\#' . $CFG->CSV_ENCODE . '/';
|
||||
}
|
||||
} else {
|
||||
$csv_delimiter = ",";
|
||||
}
|
||||
|
||||
$text = $mform_post->get_file_content('userfile');
|
||||
$text = preg_replace('!\r\n?!',"\n",$text);
|
||||
$text = $importform->get_file_content('userfile');
|
||||
$text = preg_replace('!\r\n?!', "\n", $text);
|
||||
|
||||
$rawlines = explode("\n", $text);
|
||||
|
||||
require_once($CFG->libdir . '/csvlib.class.php');
|
||||
$importid = csv_import_reader::get_new_iid('groupimport');
|
||||
$csvimport = new csv_import_reader($importid, 'groupimport');
|
||||
$delimiter = $formdata->delimiter_name;
|
||||
$encoding = $formdata->encoding;
|
||||
$readcount = $csvimport->load_csv_content($text, $encoding, $delimiter);
|
||||
|
||||
if ($readcount === false) {
|
||||
print_error('csvfileerror', 'error', $PAGE->url, $csvimport->get_error());
|
||||
} else if ($readcount == 0) {
|
||||
print_error('csvemptyfile', 'error', $PAGE->url, $csvimport->get_error());
|
||||
} else if ($readcount == 1) {
|
||||
print_error('csvnodata', 'error', $PAGE->url);
|
||||
}
|
||||
|
||||
$csvimport->init();
|
||||
|
||||
unset($text);
|
||||
|
||||
// make arrays of valid fields for error checking
|
||||
@@ -88,12 +95,12 @@ if ($mform_post->is_cancelled()) {
|
||||
);
|
||||
|
||||
// --- get header (field names) ---
|
||||
$header = explode($csv_delimiter, array_shift($rawlines));
|
||||
$header = explode($csvimport::get_delimiter($delimiter), array_shift($rawlines));
|
||||
// check for valid field names
|
||||
foreach ($header as $i => $h) {
|
||||
$h = trim($h); $header[$i] = $h; // remove whitespace
|
||||
if (!(isset($required[$h]) or isset($optionalDefaults[$h]) or isset($optional[$h]))) {
|
||||
print_error('invalidfieldname', 'error', 'import.php?id='.$id, $h);
|
||||
print_error('invalidfieldname', 'error', $PAGE->url, $h);
|
||||
}
|
||||
if (isset($required[$h])) {
|
||||
$required[$h] = 2;
|
||||
@@ -102,32 +109,28 @@ if ($mform_post->is_cancelled()) {
|
||||
// check for required fields
|
||||
foreach ($required as $key => $value) {
|
||||
if ($value < 2) {
|
||||
print_error('fieldrequired', 'error', 'import.php?id='.$id, $key);
|
||||
print_error('fieldrequired', 'error', $PAGE->url, $key);
|
||||
}
|
||||
}
|
||||
$linenum = 2; // since header is line 1
|
||||
|
||||
foreach ($rawlines as $rawline) {
|
||||
while ($line = $csvimport->next()) {
|
||||
|
||||
$newgroup = new stdClass();//to make Martin happy
|
||||
foreach ($optionalDefaults as $key => $value) {
|
||||
$newgroup->$key = current_language(); //defaults to current language
|
||||
}
|
||||
//Note: commas within a field should be encoded as , (for comma separated csv files)
|
||||
//Note: semicolon within a field should be encoded as ; (for semicolon separated csv files)
|
||||
$line = explode($csv_delimiter, $rawline);
|
||||
foreach ($line as $key => $value) {
|
||||
//decode encoded commas
|
||||
$record[$header[$key]] = preg_replace($csv_encode, $csv_delimiter, trim($value));
|
||||
$record[$header[$key]] = trim($value);
|
||||
}
|
||||
if (trim($rawline) !== '') {
|
||||
if (trim(implode($line)) !== '') {
|
||||
// add a new group to the database
|
||||
|
||||
// add fields to object $user
|
||||
foreach ($record as $name => $value) {
|
||||
// check for required values
|
||||
if (isset($required[$name]) and !$value) {
|
||||
print_error('missingfield', 'error', 'import.php?id='.$id, $name);
|
||||
print_error('missingfield', 'error', $PAGE->url, $name);
|
||||
} else if ($name == "groupname") {
|
||||
$newgroup->name = $value;
|
||||
} else {
|
||||
@@ -232,6 +235,7 @@ if ($mform_post->is_cancelled()) {
|
||||
}
|
||||
}
|
||||
|
||||
$csvimport->close();
|
||||
echo $OUTPUT->single_button($returnurl, get_string('continue'), 'get');
|
||||
echo $OUTPUT->footer();
|
||||
die;
|
||||
@@ -240,5 +244,5 @@ if ($mform_post->is_cancelled()) {
|
||||
/// Print the form
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading_with_help($strimportgroups, 'importgroups', 'core_group');
|
||||
$mform_post ->display();
|
||||
$importform->display();
|
||||
echo $OUTPUT->footer();
|
||||
|
||||
@@ -27,6 +27,7 @@ if (!defined('MOODLE_INTERNAL')) {
|
||||
}
|
||||
|
||||
require_once($CFG->libdir.'/formslib.php');
|
||||
require_once($CFG->libdir . '/csvlib.class.php');
|
||||
|
||||
/**
|
||||
* Groups import form class
|
||||
@@ -56,6 +57,19 @@ class groups_import_form extends moodleform {
|
||||
$mform->addElement('hidden', 'id');
|
||||
$mform->setType('id', PARAM_INT);
|
||||
|
||||
$choices = csv_import_reader::get_delimiter_list();
|
||||
$mform->addElement('select', 'delimiter_name', get_string('csvdelimiter', 'group'), $choices);
|
||||
if (array_key_exists('cfg', $choices)) {
|
||||
$mform->setDefault('delimiter_name', 'cfg');
|
||||
} else if (get_string('listsep', 'langconfig') == ';') {
|
||||
$mform->setDefault('delimiter_name', 'semicolon');
|
||||
} else {
|
||||
$mform->setDefault('delimiter_name', 'comma');
|
||||
}
|
||||
|
||||
$choices = core_text::get_encodings();
|
||||
$mform->addElement('select', 'encoding', get_string('encoding', 'group'), $choices);
|
||||
$mform->setDefault('encoding', 'UTF-8');
|
||||
$this->add_action_buttons(true, get_string('importgroups', 'core_group'));
|
||||
|
||||
$this->set_data($data);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['clianswerno'] = 'n';
|
||||
$string['cliansweryes'] = 'j';
|
||||
$string['cliincorrectvalueretry'] = 'Falscha Wert, probiers nummal';
|
||||
$string['cliyesnoprompt'] = 'druck j (bedeit ja) oda n (bedeit na)';
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['parentlanguage'] = 'de';
|
||||
$string['thislanguage'] = 'Bairisch';
|
||||
@@ -41,5 +41,5 @@ $string['cliunknowoption'] = 'Aukera ezezagunak:
|
||||
Mesedez, erabili --help aukera.';
|
||||
$string['cliyesnoprompt'] = 'idatzi b (bai esateko) edo e (ez esateko)';
|
||||
$string['environmentrequireinstall'] = 'derrigorrezkoa da instalatuta eta gaituta izatea';
|
||||
$string['environmentrequireversion'] = '{$a->needed} bertsioa beharrezkoa da eta zu {$a->current} ari zara egikaritzen';
|
||||
$string['environmentrequireversion'] = '{$a->needed} bertsioa beharrezkoa da eta zu {$a->current} ari zara exekutatzen';
|
||||
$string['upgradekeyset'] = 'Eguneraketa-kodea (utzi hutsik kodea erabili nahi ez baduzu)';
|
||||
|
||||
@@ -31,5 +31,6 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['language'] = 'भाषा';
|
||||
$string['moodlelogo'] = 'Moodle लोगो';
|
||||
$string['next'] = 'अगला';
|
||||
$string['reload'] = 'सीमा से अधिक लादना';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['parentlanguage'] = 'hi';
|
||||
$string['thislanguage'] = 'हिंदी';
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['thislanguage'] = 'Qaraqalpaq tili';
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['language'] = 'زبان';
|
||||
$string['moodlelogo'] = 'لوگوی مودل (Moodle)';
|
||||
$string['next'] = 'بعدی';
|
||||
$string['previous'] = 'قبلی';
|
||||
$string['reload'] = 'بارگیری مجدد';
|
||||
+1
-1
@@ -301,7 +301,7 @@ $string['error:invalidexpiredate'] = 'Expiry date has to be in the future.';
|
||||
$string['error:invalidexpireperiod'] = 'Expiry period cannot be negative or equal 0.';
|
||||
$string['error:invalidparambadge'] = 'Badge does not exist. ';
|
||||
$string['error:noactivities'] = 'There are no activities with completion criteria enabled in this course.';
|
||||
$string['error:nobadges'] = 'There are no course or site badges with access enabled to be added as criteria.';
|
||||
$string['error:nobadges'] = 'There are currently no badges with access enabled to be added as criteria. A site badge can only have other site badges as criteria. A course badge can have other course badges or site badges as criteria.';
|
||||
$string['error:invalidparamcohort'] = 'Cohort does not exist. ';
|
||||
$string['error:noactivities'] = 'There are no activities with completion criteria enabled in this course.';
|
||||
$string['error:nocohorts'] = 'No cohorts';
|
||||
|
||||
@@ -52,6 +52,9 @@ $string['courses'] = 'Courses';
|
||||
$string['customexport'] = 'Custom range ({$a->timestart} - {$a->timeend})';
|
||||
$string['daily'] = 'Daily';
|
||||
$string['day'] = 'Day';
|
||||
$string['dayeventsmany'] = '{$a->num} events, {$a->day}';
|
||||
$string['dayeventsnone'] = 'No events, {$a}';
|
||||
$string['dayeventsone'] = '1 event, {$a}';
|
||||
$string['daynext'] = 'Next day';
|
||||
$string['dayprev'] = 'Previous day';
|
||||
$string['dayviewfor'] = 'Day view for:';
|
||||
|
||||
+4
-1
@@ -194,10 +194,12 @@ $string['coursemisconf'] = 'Course is misconfigured';
|
||||
$string['courserequestdisabled'] = 'Sorry, but course requests have been disabled by the administrator.';
|
||||
$string['csvcolumnduplicates'] = 'Duplicate columns detected';
|
||||
$string['csvemptyfile'] = 'The CSV file is empty';
|
||||
$string['csvfileerror'] = 'There is something wrong with the format of the CSV file. Please check the number of headings and columns match, and that the delimiter and file encoding are correct: {$a}';
|
||||
$string['csvfewcolumns'] = 'Not enough columns, please verify the delimiter setting';
|
||||
$string['csvinvalidcols'] = '<b>Invalid CSV file:</b> First line must include "Header Fields" and the file must be type of <br />"Expanded Fields/Comma Separated"<br />or<br /> "Expanded Fields with CAVV Result Code/Comma Separated"';
|
||||
$string['csvinvalidcolsnum'] = 'Invalid CSV file - each line must include 49 or 70 fields';
|
||||
$string['csvloaderror'] = 'An error occurred while loading the CSV file: {$a}';
|
||||
$string['csvnodata'] = 'Invalid CSV file - The CSV file has headers but does not contain any data.';
|
||||
$string['csvweirdcolumns'] = 'Invalid CSV file format - number of columns is not constant!';
|
||||
$string['dbconnectionfailed'] = '<p>Error: Database connection failed</p>
|
||||
<p>It is possible that the database is overloaded or otherwise not running properly.</p>
|
||||
@@ -220,6 +222,7 @@ $string['ddlxmlfileerror'] = 'XML database file errors found';
|
||||
$string['destinationcmnotexit'] = 'The destination course module does not exist';
|
||||
$string['detectedbrokenplugin'] = 'Plugin "{$a}" is defective or outdated, can not continue, sorry.';
|
||||
$string['dmlexceptiononinstall'] = '<p>A database error has occurred [{$a->errorcode}].<br />{$a->debuginfo}</p>';
|
||||
$string['dmlparseexception'] = 'Error parsing SQL query';
|
||||
$string['dmlreadexception'] = 'Error reading from database';
|
||||
$string['dmltransactionexception'] = 'Database transaction error';
|
||||
$string['dmlwriteexception'] = 'Error writing to database';
|
||||
@@ -434,7 +437,7 @@ $string['noparticipants'] = 'No participants found for this course';
|
||||
$string['noparticipatorycms'] = 'Sorry, but you have no participatory course modules to report on';
|
||||
$string['nopermissions'] = 'Sorry, but you do not currently have permissions to do that ({$a}).';
|
||||
$string['nopermissiontocomment'] = 'You can\'t add comments';
|
||||
$string['nopermissiontodelentry'] = 'You can\'t delete other people\'s entries!';
|
||||
$string['nopermissiontodelentry'] = 'You can\'t delete this comment!';
|
||||
$string['nopermissiontoeditcomment'] = 'You can\'t edit other people\'s comments!';
|
||||
$string['nopermissiontohide'] = 'No permission to hide!';
|
||||
$string['nopermissiontoimportact'] = 'You do not have the required permissions to import activities to this course';
|
||||
|
||||
@@ -43,6 +43,7 @@ $string['creategrouping'] = 'Create grouping';
|
||||
$string['creategroupinselectedgrouping'] = 'Create group in grouping';
|
||||
$string['createingrouping'] = 'Grouping of auto-created groups';
|
||||
$string['createorphangroup'] = 'Create orphan group';
|
||||
$string['csvdelimiter'] = 'CSV delimiter';
|
||||
$string['databaseupgradegroups'] = 'Groups version is now {$a}';
|
||||
$string['defaultgrouping'] = 'Default grouping';
|
||||
$string['defaultgroupingname'] = 'Grouping';
|
||||
@@ -59,6 +60,7 @@ $string['editgroupsettings'] = 'Edit group settings';
|
||||
$string['editusersgroupsa'] = 'Edit groups for "{$a}"';
|
||||
$string['enablemessaging'] = 'Group messaging';
|
||||
$string['enablemessaging_help'] = 'If enabled, group members can send messages to the others in their group via the messaging drawer.';
|
||||
$string['encoding'] = 'Encoding';
|
||||
$string['enrolmentkey'] = 'Enrolment key';
|
||||
$string['enrolmentkey_help'] = 'An enrolment key enables access to the course to be restricted to only those who know the key. If a group enrolment key is specified, then not only will entering that key let the user into the course, but it will also automatically make them a member of this group.
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ $string['group:html_track'] = 'HTML track files';
|
||||
$string['group:html_video'] = 'Video files natively supported by browsers';
|
||||
$string['group:image'] = 'Image files';
|
||||
$string['group:media_source'] = 'Streaming media';
|
||||
$string['group:optimised_image'] = 'Image files to be optimised, such as badges';
|
||||
$string['group:presentation'] = 'Presentation files';
|
||||
$string['group:sourcecode'] = 'Source code';
|
||||
$string['group:spreadsheet'] = 'Spreadsheet files';
|
||||
|
||||
@@ -56,6 +56,8 @@ $string['cannotdelete'] = 'Cannot delete this file.';
|
||||
$string['cannotdownload'] = 'Cannot download this file';
|
||||
$string['cannotdownloaddir'] = 'Cannot download this folder';
|
||||
$string['cannotinitplugin'] = 'Call plugin_init failed';
|
||||
$string['cannotunzipcontentunreadable'] = 'Cannot unzip this file because the contents of the file cannot be read.';
|
||||
$string['cannotunzipquotaexceeded'] = 'Cannot unzip this file because the maximum size allowed in this draft area will be exceeded.';
|
||||
$string['cleancache'] = 'Clean my cache files';
|
||||
$string['close'] = 'Close';
|
||||
$string['commonrepositorysettings'] = 'Common repository settings';
|
||||
|
||||
@@ -521,6 +521,16 @@ function has_capability($capability, context $context, $user = null, $doanything
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($USER->loginascontext)) {
|
||||
// The current user is logged in as another user and can assume their identity at or below the `loginascontext`
|
||||
// defined in the USER session.
|
||||
// The user may not assume their identity at any other location.
|
||||
if (!$USER->loginascontext->is_parent_of($context, true)) {
|
||||
// The context being checked is not the specified context, or one of its children.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Find out if user is admin - it is not possible to override the doanything in any way
|
||||
// and it is not possible to switch to admin role either.
|
||||
if ($doanything) {
|
||||
@@ -5552,6 +5562,30 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current context is a parent of the possible child.
|
||||
*
|
||||
* @param context $possiblechild
|
||||
* @param bool $includeself Whether to check the current context
|
||||
* @return bool
|
||||
*/
|
||||
public function is_parent_of(context $possiblechild, bool $includeself): bool {
|
||||
// A simple substring check is used on the context path.
|
||||
// The possible child's path is used as a haystack, with the current context as the needle.
|
||||
// The path is prefixed with '+' to ensure that the parent always starts at the top.
|
||||
// It is suffixed with '+' to ensure that parents are not included.
|
||||
// The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
|
||||
// The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
|
||||
// The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
|
||||
$haystacksuffix = $includeself ? '/+' : '+';
|
||||
|
||||
$strpos = strpos(
|
||||
"+{$possiblechild->path}{$haystacksuffix}",
|
||||
"+{$this->path}/"
|
||||
);
|
||||
return $strpos === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parent contexts of this context in reversed order, i.e. parent first,
|
||||
* then grand parent, etc.
|
||||
@@ -5576,6 +5610,30 @@ abstract class context extends stdClass implements IteratorAggregate {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current context is a child of the possible parent.
|
||||
*
|
||||
* @param context $possibleparent
|
||||
* @param bool $includeself Whether to check the current context
|
||||
* @return bool
|
||||
*/
|
||||
public function is_child_of(context $possibleparent, bool $includeself): bool {
|
||||
// A simple substring check is used on the context path.
|
||||
// The current context is used as a haystack, with the possible parent as the needle.
|
||||
// The path is prefixed with '+' to ensure that the parent always starts at the top.
|
||||
// It is suffixed with '+' to ensure that children are not included.
|
||||
// The needle always suffixes with a '/' to ensure that the contextid uses a complete match (i.e. 142/ instead of 14).
|
||||
// The haystack is suffixed with '/+' if $includeself is true to allow the current context to match.
|
||||
// The haystack is suffixed with '+' if $includeself is false to prevent the current context from matching.
|
||||
$haystacksuffix = $includeself ? '/+' : '+';
|
||||
|
||||
$strpos = strpos(
|
||||
"+{$this->path}{$haystacksuffix}",
|
||||
"+{$possibleparent->path}/"
|
||||
);
|
||||
return $strpos === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parent context ids of this context in reversed order, i.e. parent first,
|
||||
* then grand parent, etc.
|
||||
|
||||
+2
-2
@@ -1200,7 +1200,7 @@ class admin_externalpage implements part_of_admin_tree {
|
||||
/** @var string The external URL that we should link to when someone requests this external page. */
|
||||
public $url;
|
||||
|
||||
/** @var string The role capability/permission a user must have to access this external page. */
|
||||
/** @var array The role capability/permission a user must have to access this external page. */
|
||||
public $req_capability;
|
||||
|
||||
/** @var object The context in which capability/permission should be checked, default is site context. */
|
||||
@@ -1424,7 +1424,7 @@ class admin_settingpage implements part_of_admin_tree {
|
||||
/** @var admin_settingdependency[] list of settings to hide when certain conditions are met */
|
||||
protected $dependencies = [];
|
||||
|
||||
/** @var string The role capability/permission a user must have to access this external page. */
|
||||
/** @var array The role capability/permission a user must have to access this external page. */
|
||||
public $req_capability;
|
||||
|
||||
/** @var object The context in which capability/permission should be checked, default is site context. */
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("core/checkbox-toggleall",["jquery","core/pubsub"],function(a,b){var c=!1,d={checkboxToggled:"core/checkbox-toggleall:checkboxToggled"},e=function(a,b,c){if(c){return a.find("[data-action=\"toggle\"][data-togglegroup=\""+b+"\"]")}else{return a.find("[data-action=\"toggle\"][data-togglegroup^=\""+b+"\"]")}},f=function(a,b){return e(a,b,!1).filter("[data-toggle=\"slave\"]")},g=function(a,b,c){return e(a,b,c).filter("[data-toggle=\"master\"]")},h=function(a,b){return e(a,b,!0).filter("[data-toggle=\"action\"]")},i=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),i;if(g.is(":checkbox")){i=g.is(":checked")}else{i=1===g.data("checkall")}var j=f(e,h),k=j.filter(":checked");l(e,h,i,!1);j.prop("checked",i);j.trigger("change");b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:j,checkedSlaves:k,anyChecked:i})},j=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup").split(" "),i=[],j="";h.forEach(function(a){j+=" "+a;i.push(j.trim())});i.forEach(function(a){var c=f(e,a),g=c.filter(":checked"),h=c.length===g.length;l(e,a,h,!0);k(e,a,!g.length);b.publish(d.checkboxToggled,{root:e,toggleGroupName:a,slaves:c,checkedSlaves:g,anyChecked:!!g.length})})},k=function(a,b,c){h(a,b).prop("disabled",c)},l=function(b,c,d,e){var f=g(b,c,e);f.prop("checked",d);f.each(function(c,e){e=a(e);var f;if(d){f=e.data("toggle-deselectall")}else{f=e.data("toggle-selectall")}if(e.is(":checkbox")){var g=b.find("[for=\""+e.attr("id")+"\"]");if(g.length){if(g.html()!==f){g.html(f)}}}else{e.text(f);e.data("checkall",d?0:1)}})},m=function(){if(!c){c=!0;var b=a(document.body);b.on("click","[data-action=\"toggle\"][data-toggle=\"master\"]",{root:b},i);b.on("change","[data-action=\"toggle\"][data-toggle=\"slave\"]",{root:b},j)}};return{init:function init(){m()},events:d}});
|
||||
define ("core/checkbox-toggleall",["jquery","core/pubsub"],function(a,b){var c=!1,d={checkboxToggled:"core/checkbox-toggleall:checkboxToggled"},e=function(a,b,c){if(c){return a.find("[data-action=\"toggle\"][data-togglegroup=\""+b+"\"]")}else{return a.find("[data-action=\"toggle\"][data-togglegroup^=\""+b+"\"]")}},f=function(a,b){return e(a,b,!1).filter("[data-toggle=\"slave\"]")},g=function(a,b,c){return e(a,b,c).filter("[data-toggle=\"master\"]")},h=function(a,b){return e(a,b,!0).filter("[data-toggle=\"action\"]")},i=function(a,b){var c=f(a,b);if(0<c.length){var d=c.filter(":checked"),e=c.length===d.length;n(a,b,e,!0);m(a,b,!d.length)}},j=function(a){var b=a.split(" "),c=[],d="";b.forEach(function(a){d+=" "+a;c.push(d.trim())});return c},k=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),k;if(g.is(":checkbox")){k=g.is(":checked")}else{k=1===g.data("checkall")}var l=f(e,h);l.prop("checked",k).trigger("change");var o=l.filter(":checked");n(e,h,k,!1);m(e,h,!o.length);var p=j(h).filter(function(a){return a!==h});p.forEach(function(a){i(e,a)});b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:l,checkedSlaves:o,anyChecked:k})},l=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),k=f(e,h),l=k.filter(":checked"),m=j(h);m.forEach(function(a){i(e,a)});b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:k,checkedSlaves:l,anyChecked:!!l.length})},m=function(a,b,c){h(a,b).prop("disabled",c)},n=function(b,c,d,e){var f=g(b,c,e);f.prop("checked",d);f.each(function(c,e){e=a(e);var f;if(d){f=e.data("toggle-deselectall")}else{f=e.data("toggle-selectall")}if(e.is(":checkbox")){var g=b.find("[for=\""+e.attr("id")+"\"]");if(g.length){if(g.html()!==f){g.html(f)}}}else{e.text(f);e.data("checkall",d?0:1)}})},o=function(){if(!c){c=!0;var b=a(document.body);b.on("click","[data-action=\"toggle\"][data-toggle=\"master\"]",{root:b},k);b.on("click","[data-action=\"toggle\"][data-toggle=\"slave\"]",{root:b},l)}};return{init:function init(){o()},events:d}});
|
||||
//# sourceMappingURL=checkbox-toggleall.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("core/icon_system_fontawesome",["core/icon_system","jquery","core/ajax","core/mustache","core/localstorage","core/url"],function(a,b,c,d,e,f){var g=null,h=null,i=function(){a.apply(this,arguments)};i.prototype=Object.create(a.prototype);i.prototype.init=function(){if(g){return b.when(this)}var a=e.get("core/iconmap-fontawesome");if(a){a=JSON.parse(a)}if(a){g=a;return b.when(this)}if(null===h){h=c.call([{methodname:"core_output_load_fontawesome_icon_map",args:[]}],!0,!1,!1,0,M.cfg.themerev)[0]}return h.then(function(a){g={};b.each(a,function(a,b){g[b.component+"/"+b.pix]=b.to});e.set("core/iconmap-fontawesome",JSON.stringify(g));return this}.bind(this))};i.prototype.renderIcon=function(a,b,c,e){var h=g[b+"/"+a],i=!1;if("undefined"==typeof h){var j=f.imageUrl(a,b);i={attributes:[{name:"src",value:j},{name:"alt",value:c},{name:"title",value:c}]}}var k={key:h,title:c,alt:c,unmappedIcon:i};if("undefined"==typeof c||""===c){k["aria-hidden"]=!0}var l=d.render(e,k);return l.trim()};i.prototype.getTemplateName=function(){return"core/pix_icon_fontawesome"};return i});
|
||||
define ("core/icon_system_fontawesome",["core/icon_system","jquery","core/ajax","core/mustache","core/localstorage","core/url"],function(a,b,c,d,e,f){var g=null,h=null,i=function(){a.apply(this,arguments)};i.prototype=Object.create(a.prototype);i.prototype.init=function(){if(g){return b.when(this)}var a=e.get("core/iconmap-fontawesome");if(a){a=JSON.parse(a)}if(a){g=a;return b.when(this)}if(null===h){h=c.call([{methodname:"core_output_load_fontawesome_icon_system_map",args:{themename:M.cfg.theme}}],!0,!1,!1,0,M.cfg.themerev)[0]}return h.then(function(a){g={};b.each(a,function(a,b){g[b.component+"/"+b.pix]=b.to});e.set("core/iconmap-fontawesome",JSON.stringify(g));return this}.bind(this))};i.prototype.renderIcon=function(a,b,c,e){var h=g[b+"/"+a],i=!1;if("undefined"==typeof h){var j=f.imageUrl(a,b);i={attributes:[{name:"src",value:j},{name:"alt",value:c},{name:"title",value:c}]}}var k={key:h,title:c,alt:c,unmappedIcon:i};if("undefined"==typeof c||""===c){k["aria-hidden"]=!0}var l=d.render(e,k);return l.trim()};i.prototype.getTemplateName=function(){return"core/pix_icon_fontawesome"};return i});
|
||||
//# sourceMappingURL=icon_system_fontawesome.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -95,6 +95,45 @@ define(['jquery', 'core/pubsub'], function($, PubSub) {
|
||||
return getToggleGroupElements(root, toggleGroup, true).filter('[data-toggle="action"]');
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles the master checkboxes and action elements in a given toggle group.
|
||||
*
|
||||
* @param {jQuery} root The root jQuery element.
|
||||
* @param {String} toggleGroupName The name of the toggle group
|
||||
*/
|
||||
var toggleMastersAndActionElements = function(root, toggleGroupName) {
|
||||
var toggleGroupSlaves = getAllSlaveCheckboxes(root, toggleGroupName);
|
||||
if (toggleGroupSlaves.length > 0) {
|
||||
var toggleGroupCheckedSlaves = toggleGroupSlaves.filter(':checked');
|
||||
var targetState = toggleGroupSlaves.length === toggleGroupCheckedSlaves.length;
|
||||
|
||||
// Make sure to toggle the exact master checkbox in the given toggle group.
|
||||
setMasterStates(root, toggleGroupName, targetState, true);
|
||||
// Enable the action elements if there's at least one checkbox checked in the given toggle group.
|
||||
// Disable otherwise.
|
||||
setActionElementStates(root, toggleGroupName, !toggleGroupCheckedSlaves.length);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array containing every toggle group level of a given toggle group.
|
||||
*
|
||||
* @param {String} toggleGroupName The name of the toggle group
|
||||
* @return {Array} toggleGroupLevels Array that contains every toggle group level of a given toggle group
|
||||
*/
|
||||
var getToggleGroupLevels = function(toggleGroupName) {
|
||||
var toggleGroups = toggleGroupName.split(' ');
|
||||
var toggleGroupLevels = [];
|
||||
var toggleGroupLevel = '';
|
||||
|
||||
toggleGroups.forEach(function(toggleGroupName) {
|
||||
toggleGroupLevel += ' ' + toggleGroupName;
|
||||
toggleGroupLevels.push(toggleGroupLevel.trim());
|
||||
});
|
||||
|
||||
return toggleGroupLevels;
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles the slave checkboxes in a given toggle group when a master element in that toggle group is toggled.
|
||||
*
|
||||
@@ -114,14 +153,26 @@ define(['jquery', 'core/pubsub'], function($, PubSub) {
|
||||
}
|
||||
|
||||
var slaves = getAllSlaveCheckboxes(root, toggleGroupName);
|
||||
// Set the slave checkboxes from the masters and manually trigger the native 'change' event.
|
||||
slaves.prop('checked', targetState).trigger('change');
|
||||
// Get all checked slaves after the change of state.
|
||||
var checkedSlaves = slaves.filter(':checked');
|
||||
|
||||
// Toggle the master checkbox in the given toggle group.
|
||||
setMasterStates(root, toggleGroupName, targetState, false);
|
||||
// Enable the action elements if there's at least one checkbox checked in the given toggle group. Disable otherwise.
|
||||
setActionElementStates(root, toggleGroupName, !checkedSlaves.length);
|
||||
|
||||
// Set the slave checkboxes from the masters.
|
||||
slaves.prop('checked', targetState);
|
||||
// Trigger 'change' event to toggle other master checkboxes (e.g. parent master checkboxes) and action elements.
|
||||
slaves.trigger('change');
|
||||
// Get all toggle group levels and toggle accordingly all parent master checkboxes and action elements from each
|
||||
// level. Exclude the given toggle group (toggleGroupName) as the master checkboxes and action elements from this
|
||||
// level have been already toggled.
|
||||
var toggleGroupLevels = getToggleGroupLevels(toggleGroupName)
|
||||
.filter(toggleGroupLevel => toggleGroupLevel !== toggleGroupName);
|
||||
|
||||
toggleGroupLevels.forEach(function(toggleGroupLevel) {
|
||||
// Toggle the master checkboxes action elements in the given toggle group level.
|
||||
toggleMastersAndActionElements(root, toggleGroupLevel);
|
||||
});
|
||||
|
||||
PubSub.publish(events.checkboxToggled, {
|
||||
root: root,
|
||||
@@ -142,33 +193,24 @@ define(['jquery', 'core/pubsub'], function($, PubSub) {
|
||||
var toggleMastersFromSlaves = function(e) {
|
||||
var root = e.data.root;
|
||||
var target = $(e.target);
|
||||
var toggleGroupName = target.data('togglegroup');
|
||||
var slaves = getAllSlaveCheckboxes(root, toggleGroupName);
|
||||
var checkedSlaves = slaves.filter(':checked');
|
||||
|
||||
var toggleGroups = target.data('togglegroup').split(' ');
|
||||
var toggleGroupLevels = [];
|
||||
var toggleGroupLevel = '';
|
||||
toggleGroups.forEach(function(toggleGroupName) {
|
||||
toggleGroupLevel += ' ' + toggleGroupName;
|
||||
toggleGroupLevels.push(toggleGroupLevel.trim());
|
||||
// Get all toggle group levels for the given toggle group and toggle accordingly all master checkboxes
|
||||
// and action elements from each level.
|
||||
var toggleGroupLevels = getToggleGroupLevels(toggleGroupName);
|
||||
toggleGroupLevels.forEach(function(toggleGroupLevel) {
|
||||
// Toggle the master checkboxes action elements in the given toggle group level.
|
||||
toggleMastersAndActionElements(root, toggleGroupLevel);
|
||||
});
|
||||
|
||||
toggleGroupLevels.forEach(function(toggleGroupName) {
|
||||
var slaves = getAllSlaveCheckboxes(root, toggleGroupName);
|
||||
var checkedSlaves = slaves.filter(':checked');
|
||||
var targetState = (slaves.length === checkedSlaves.length);
|
||||
|
||||
// Make sure to toggle the exact master checkbox.
|
||||
setMasterStates(root, toggleGroupName, targetState, true);
|
||||
|
||||
// Enable action elements when there's at least one checkbox checked. Disable otherwise.
|
||||
setActionElementStates(root, toggleGroupName, !checkedSlaves.length);
|
||||
|
||||
PubSub.publish(events.checkboxToggled, {
|
||||
root: root,
|
||||
toggleGroupName: toggleGroupName,
|
||||
slaves: slaves,
|
||||
checkedSlaves: checkedSlaves,
|
||||
anyChecked: !!checkedSlaves.length,
|
||||
});
|
||||
PubSub.publish(events.checkboxToggled, {
|
||||
root: root,
|
||||
toggleGroupName: toggleGroupName,
|
||||
slaves: slaves,
|
||||
checkedSlaves: checkedSlaves,
|
||||
anyChecked: !!checkedSlaves.length,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -233,7 +275,7 @@ define(['jquery', 'core/pubsub'], function($, PubSub) {
|
||||
|
||||
var root = $(document.body);
|
||||
root.on('click', '[data-action="toggle"][data-toggle="master"]', {root: root}, toggleSlavesFromMasters);
|
||||
root.on('change', '[data-action="toggle"][data-toggle="slave"]', {root: root}, toggleMastersFromSlaves);
|
||||
root.on('click', '[data-action="toggle"][data-toggle="slave"]', {root: root}, toggleMastersFromSlaves);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -36,7 +36,9 @@ function($, log, str, templates, notification, LoadingIcon) {
|
||||
SPACE: 32,
|
||||
ESCAPE: 27,
|
||||
COMMA: 44,
|
||||
UP: 38
|
||||
UP: 38,
|
||||
LEFT: 37,
|
||||
RIGHT: 39
|
||||
};
|
||||
|
||||
var uniqueId = Date.now();
|
||||
@@ -800,10 +802,24 @@ function($, log, str, templates, notification, LoadingIcon) {
|
||||
// Remove it from the selection.
|
||||
pendingPromise.resolve(deselectItem(options, state, $(e.currentTarget), originalSelect));
|
||||
});
|
||||
// Remove the highlight of items when user tabs out the tag list.
|
||||
selectionElement.on('blur', function(e) {
|
||||
e.preventDefault();
|
||||
$(this).children().attr('data-active-selection', false).attr('id', '');
|
||||
});
|
||||
// When tag list is focused, highlight the first item.
|
||||
selectionElement.on('focus', function(e) {
|
||||
e.preventDefault();
|
||||
var element = $(this).children('[data-active-selection=true]');
|
||||
if (element && element.length === 0) {
|
||||
activateNextSelection(state);
|
||||
}
|
||||
});
|
||||
// Keyboard navigation for the selection list.
|
||||
selectionElement.on('keydown', function(e) {
|
||||
var pendingPromise = addPendingJSPromise('form-autocomplete-keydown-' + e.keyCode);
|
||||
switch (e.keyCode) {
|
||||
case KEYS.RIGHT:
|
||||
case KEYS.DOWN:
|
||||
// We handled this event, so prevent it.
|
||||
e.preventDefault();
|
||||
@@ -811,6 +827,7 @@ function($, log, str, templates, notification, LoadingIcon) {
|
||||
// Choose the next selection item.
|
||||
pendingPromise.resolve(activateNextSelection(state));
|
||||
return false;
|
||||
case KEYS.LEFT:
|
||||
case KEYS.UP:
|
||||
// We handled this event, so prevent it.
|
||||
e.preventDefault();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user